Python informer

Improve your Python coding skills

Sequences

We have already met lists, tuples and strings. These data structures have a lot of similarities - that is because they are all types of sequence.

Other sequence types that you might see are bytes objects, bytearray objects and range objects. Those are a bit more specialised so we won’t cover them here.

In this lesson, we will briefly revisit what we already know about sequences, and look at a few more advanced features.

Common features of all sequences

All sequences, including lists, tuples and strings, have the following operators and methods:

You will be familiar with most of these from the previous lessons on lists and slices.

There are a few standard Python functions that can be applied to sequences. These include:

  • len(s) finds the number of elements in the sequence s
  • min(s) finds the smallest element in s
  • max(s) finds the largest element in s

There are a few others described in the article on builtin functions for iterables.

Extra features of lists

Lists (and other mutable sequences), have the following extra operators and methods, in addition to the ones above:

The reason strings and tuples don’t have these features is that they change the content of the sequence. For example, clear removes all the elements of the list. You can’t do that with a string or a tuple, because it would break immutability - a string or tuple cannot be changed after it has been declared.

Reverse and sort

s.reverse() reverses the sequence s. It does this “in-place”, that is it changes the sequence itself. For example:

s = [1, 4, 9, 3]
s.reverse()
print(s)        # [3, 9, 4, 1]

Similarly, s.sort() sorts the sequence s, again in-place. For example:

s = [1, 4, 9, 3]
s.sort()
print(s)        # [1, 3, 4, 9]

sort() has extra parameters that control sort order, as described for the sorted function.

Iterables and iterators

You may have seen the terms iterable or iterator used almost interchangeably with the term sequence. In fact, a sequence is a special type of iterable, so at this stage you can think of them as being almost the same thing - a sequence is an iterable with a few extra features. There is more detail here.

Conversion and copying

The list function is used to create a new list:

t = (1, 2, 3)
s = 'abc'
k = [10, 11, 12]
print(list())    # []
print(list(k))   # [10, 11, 12]
print(list(t))   # [1, 2, 3]
print(list(s))   # ['a', 'b', 'c']

If list is called with no parameters, it creates an empty list.

If list is called with a list as its parameter, it will create a new list with the same elements as the original. The new list is a copy of the original - a different list that has the same elements. It is actually what is called a shallow copy, described below.

If it is called with a tuple, it will create a list with the same elements as the tuple. If it is called with a string, it will create a list containing each character from the string.

The tuple function does a similar thing to list, except that it creates a tuple:

t = (1, 2, 3)
s = 'abc'
k = [10, 11, 12]
print(tuple())    # (,)
print(tuple(k))   # (10, 11, 12)
print(tuple(t))   # (1, 2, 3)
print(tuple(s))   # ('a', 'b', 'c')

The str function converts anything to a string. However, it doesn’t work in quite the same way as list or tuple, it creates a string representation of the item:

t = (1, 2, 3)
s = 'abc'
k = [10, 11, 12]
n = 3
print(str())    # ''
print(str(k))   # '[10, 11, 12]'
print(str(t))   # '(1, 2, 3)'
print(str(s))   # 'abc'
print(str(n))   # '3'

So when you apply str to k, it just creates a readable representation of the list, ie the string '[10, 11, 12]'. str works with other types of object, for example the integer 3 is converted to a string '3'.

There is an alternative way to create a copy of a sequence, using slices. Here is an example:

x = s[:]

Slice notation uses [1:4] to indicate a slice from element 1 up to but not including element 4. If we don’t specify the first value the slice starts from the start of the sequence. If we don’t specify an end value the slice continues to the end of the sequence. If we don’t specify either, as in [:], we get a copy of the entire sequence.

The useful thing is that the copy will always be the same type as the original sequence. In the code fragment above we can’t tell if s is a list, tuple, string or something more exotic, but whatever it is, x will be a copy of the same type.

2D lists

A list or tuple can contain other lists or tuples as members:

k = [1, [5, 6, 7], (10, 11, 12)]

In this case, the first element is a number, the second element is a list, the third is a tuple. For the rest of this section we will only talk about lists, but of course you can mix and match tuples too. If we create a list where each element is also a list, we can create a two dimensional list (a bit like a spreadsheet):

x = [[1, 2, 3, 4],
     [5, 6, 7, 8],
     [9, 10, 11, 12]]
print(x[1])            # [5, 6, 7, 8]
print(x[1][3])         # 8

Here x is a 2D list that has 3 rows and 4 columns.

x[1] gets element 1 from the list, that is row 1 [5, 6, 7, 8].

x[1][3] gets element 3 from sublist 1, that is row 1 col 3, value 8.

Notice the syntax used. Normally, Python expects an entire statement to be on one line. However, if a line ends at a point where a closing bracket is still required, Python will automatically continue onto the next line. This is useful for splitting long lines of code in to separate lines.

Comparing sequences

You can compare two sequences using the comparison operators ==, <, > etc.

Equality

For two sequences to be equal, they must be:

  • the same type (both lists, or both tuples etc)
  • the same length
  • corresponding elements in each sequence must be equal

For example:

[1, 2, 3] == [1, 2, 3]       # True 
[] = []                      # True, two empty lists are equal
[1, 2, 3] == [1, 2]          # False, different lengths
[1, 2, 3] == [1, 3, 2]       # False, different elements
[1, 2, 3] == (1, 2, 3)       # False, different types (list and tuple) 
[1, 2, 3] == 6               # False, different types (list vs int)

Notice that you never get an error when comparing for equality. Even if you compare two totally incompatible data types, you get a valid result (False).

Ordering

Ordering comparisons (<, >, <= or >=) work element by element. They test each pair of corresponding elements until they find a difference, and return the result comparing of that pair:

[1, 2, 3] < [2, 2, 3]        # True, because of the first element
[1, 2, 3] < [1, 2, 4]        # True, because of the last element
[1, 2, 3] < [1, 2, 3]        # False, because they are equal
[1, 2] < [1, 2, 3]           # True, because the first elements are
                             # equal but the second list is longer
[1, 2, 3] < [2, 2]           # True, because of the first element -
                             # it doesn't matter that the second list
                             # is shorter

With ordering comparisons (unlike equality), you can get errors:

[1, 2, 3] < (2, 2, 3)        # Error, can't compare list and tuple
[1, 2, 3] < 5                # Error, can't compare list and int
[1, 2, 3] < [1, 2, 'c']      # Error, can't compare 3 and 'c'
[1, 1, 3] < [1, 2, 'c']      # True, because of second element

Looping

As we saw in the lists lesson, you can loop over a sequence directly:

k = [10, 20, 30]
for n in k:
    print(n)

There are some additional functions that can be used to add functionality to the loop:

  • enumerate adds a loop count
  • zip loops over two or more sequences at once
  • filter only loops over those elements that pass a particular test
  • reversed loops over the elements in reverse order
  • sorted loops over the elemenst is sorted order

These are covered in builtin functions for iterables, and in more detail in the advanced course.