login
Holden Web
What you'll need to know tomorrow

Sequence Containers: Lists and Tuples

So far we've covered basic object types in Python, such as strings and numbers. Now we're ready to look at Python's "container" objects, starting with lists and tuples. Both lists and tuples are sequence types. Because strings are sequence types as well, much of what we learn here applies to strings too.

Sequence types have a specific order, so it's easy to identify a string's first and last characters. Similarly, lists and tuples present elements in a particular order. Each element of a sequence is numbered, always starting at zero. You refer to an individual element by following the sequence name with a number in square brackets.

Lists and Tuples

Python uses both lists and tuples. In general, tuples are used when the position of an element says something about what it represents, and lists are used to hold elements that will be treated in the same manner. Python doesn't enforce these conventions, though; the only hard rule is don't use tuples if you want to change the sequence. Tuples are for non-modifiable sequences.

Writing Lists and Tuples

Sometimes you'll want to write the contents of a list right inside your code. To do so, write a comma-separated list of element values surrounded by square brackets.

Tuples are usually written as a comma-separated list of values surrounded by parentheses rather than brackets, though in many cases the parentheses are optional. The interactive interpreter always displays a tuple with parentheses, and we recommend you write them the same way to make your code easier to read.

Let's look at some sequences in action:

Code and output
>>> lst1 = [1, 3, 5]
>>> lst2 = [2, 4, 6]
>>> tup1 = (9, 7, 5)
>>> tup2 = (8, 6, 4)
>>> dir(lst1)
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__',
'__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__',
'__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__',
'__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__',
'__setitem__', '__sizeof__', '__str__', '__subclasshook__',
'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort']
>>> dir(tup1)
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__getnewargs__', '__getstate__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__',
'__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'count', 'index']
>>> lst1+lst2
[1, 3, 5, 2, 4, 6]
>>> tup1+tup2
(9, 7, 5, 8, 6, 4)
>>> lst1+tup1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "tuple") to list
>>> clist = [lst1, lst2, tup1, tup2]
>>> print(clist)
[[1, 3, 5], [2, 4, 6], (9, 7, 5), (8, 6, 4)]

The dir() calls show that a list has methods a tuple doesn't. The public (non-dunder) methods of a list are append, clear, copy, count, extend, index, insert, pop, remove, reverse, and sort. A tuple has only count and index. We'll look at those methods later, but for now we'll focus on the general behaviour of sequences.

You can put whatever you like in a list—usually simple values like strings and numbers. The clist list above contains two other lists and two tuples.

Note The full output of dir() varies with the Python version. Current Python reports more dunder methods than the original 3.1 interpreter did, because the language has grown. The public methods shown above are what matters for everyday use.
Accessing Sequence Values

Once you have created a sequence you can access its individual elements using indexing. To index a single element, follow the sequence with a numeric value in square brackets—remember, the first element is numbered zero. You can also take slices, creating a new and usually smaller sequence. To slice a sequence, write two numeric values separated by a colon inside the square brackets. If you omit the first value the new sequence starts with the first element; if you omit the last value it ends with the last element. Try these:

Code and output
>>> clist = [1, (2, 3, 4), "a", "Bright", "c"]
>>> clist[1]
(2, 3, 4)
>>> clist[1][1]
3
>>> clist[3][1:3]
'ri'
>>> stuff = clist[2:4]
>>> stuff
['a', 'Bright']
>>> stuff2 = clist[2:5]
>>> stuff2
['a', 'Bright', 'c']
>>> stuff[0]
'a'
>>> "Strings are sequences too"[:7]
'Strings'

Indexing and slicing are fundamental operations in Python, so make sure you understand why each expression evaluates as it does. Bear in mind that when you slice a sequence the second index is not the index of the last element in the slice—it's the index of the first element excluded. This is actually very useful: clist[2:4] always gives you a two-element list. To include element four we had to reference the nonexistent element five. Because strings are sequences too, you can chop them up in exactly the same way.

Modifying Lists

Although strings and tuples are sequences, they are immutable. Once created they can't be changed—though you can still index and slice them to extract elements or sub-sequences. Lists, however, can be changed. In the same way that you can bind a new value to a name with an assignment, you can bind a new value to an element of a list:

Code and output
>>> stuff = [1, (2, 3, 4), "a", "Bright", "c"]
>>> stuff[1] = "Not a tuple"
>>> stuff
[1, 'Not a tuple', 'a', 'Bright', 'c']
>>> stuff[0] = 0
>>> stuff[3] = 'b'
>>> stuff
[0, 'Not a tuple', 'a', 'b', 'c']
>>> stuff[2:4]
['a', 'b']
>>> stuff[2:4] = [1, 2, 3]
>>> stuff
[0, 'Not a tuple', 1, 2, 3, 'c']

So far we've just been replacing single elements of the list. It's also possible to replace a slice, as the last few lines show. When you do that, the right-hand side must be a sequence—any sequence will do: a list, tuple, or string. If you assign a string to a slice, each character in the string becomes a new element of the list. Try experimenting with these possibilities.

Because you can replace any slice of a list, you can delete a slice by assigning an empty sequence to it. Python's del statement does the same job more directly. You can use it on a single element or on a slice. If you know a list contains a certain value but don't know its index, the list's remove() method will delete the first occurrence for you. If the same value occurs more than once, only the first is removed. Type the commands below:

Code and output
>>> dlist = ['a', 'b', 'c', '1', '2', 1, 2, 3]
>>> dlist[6]
2
>>> del dlist[6]
>>> dlist
['a', 'b', 'c', '1', '2', 1, 3]
>>> dlist[:3]
['a', 'b', 'c']
>>> del dlist[:3]
>>> dlist
['1', '2', 1, 3]
>>> dlist.remove(1)
>>> dlist
['1', '2', 3]
Note In the last example, element 2 (the integer 1) was removed, not element 0 (the string '1'). Python doesn't convert between strings and numbers unless you explicitly ask it to.

Also, remember that deletion only works for lists. Deleting an element from a string or tuple would amount to modifying an immutable sequence, which Python won't allow.

We can add elements to a list using the list's append() method: call it with the new element and it's appended at the end. To insert at a specific position there are two approaches: the list's insert() method, which takes an index and a value; or assigning a value to an empty slice (any slice where the lower and upper indices are equal). Try these:

Code and output
>>> elist = [] # The empty list
>>> elist.append('a')
>>> elist
['a']
>>> elist.append('b')
>>> elist
['a', 'b']
>>> elist.append((1, 2, 3))
>>> elist
['a', 'b', (1, 2, 3)]
>>> len(elist)
3
>>> elist[1:1]
[]
>>> elist[1:1] = ["new second element"]
>>> elist
['a', 'new second element', 'b', (1, 2, 3)]
>>> elist.insert(3, "4th")
>>> elist
['a', 'new second element', 'b', '4th', (1, 2, 3)]
>>> len(elist)
5

One limitation of slice assignment is that the replacement must be a sequence, which is why we usually reach for append() or insert(). If you have a whole sequence of elements to insert, though, slice assignment can be more concise than any alternative.

Note When you call append() with a sequence argument—as in elist.append((1, 2, 3)) above—the entire sequence becomes a single new element at the end of the list. If you want to add each element of a sequence individually, use extend() instead.
Slices with a Stride: Skipping Elements

A slice specifies a subsequence. Suppose you don't want every element, but rather every second or third one. The easiest way is to use a third component of the slice specification: the stride. The stride says how many positions to advance between each extracted element, and is separated from the other two components with a colon: [first:last:stride].

When you omit the stride it defaults to 1, which takes every element. A stride of 2 takes every second element, and so on. Stride values can be negative as well as positive. Slicing starts at the first component, advances by the stride, and stops when the index reaches or passes the second component. When the stride is negative, the first component must be higher than the second. Try these:

Code and output
>>> alf = "abcdefghijklmnopqrstuvwxyz"
>>> alf[2:13]
'cdefghijklm'
>>> alf[2:13:2]
'cegikm'
>>> alf[2:13:-2]
''
>>> alf[13:2:-2]
'nljhfd'
>>> alf[13:2]
''
>>> alf[::-1]
'zyxwvutsrqponmlkjihgfedcba'

One way to reverse a sequence is to slice the whole thing with a stride of -1. So to replace a list with its reverse you can write lst = lst[::-1] rather than calling lst.reverse(). Python sequences are nothing if not versatile.

Other Functions and Methods to Use with Sequences

Sometimes you'll have a string that you want to break up into a list of words. The split() method does exactly that. Called without arguments it splits on any whitespace; called with a string argument that string is used as the separator, and the pieces between its occurrences are returned as a list.

If you supply a second argument it should be an integer specifying the maximum number of splits, which limits the number of elements in the returned list.

To get the sum of the numbers in a sequence, pass the sequence to the sum() function. An exception is raised if any non-numeric element is present. To get the length of any sequence, use len().

To count how many times a particular element appears in a list or tuple, use the count() method with the element value as the argument.

Testing for Presence in a Sequence

To determine whether a sequence contains a specific value, use the in keyword, which returns either True or False. Sequences also have an index() method that returns the lowest index at which a given element occurs. Be careful, though: index() raises an exception if the element isn't present. You can guard against that with an if test first, but it's usually cleaner to handle the exception directly and avoid searching twice. We'll cover the if statement and exception handling in detail later.

Manipulating Lists and Tuples

Here is a concise program that splits a string into words using the built-in string methods:

Code
"""Simpler program to list the words of a string."""

s = input("Enter your string: ")
words = s.strip().split()
for word in words:
    print(word)

Save it as better_sentence_splitter.py. Type in a string that contains some whitespace, press Enter, and you'll see the words printed one per line.

Observe
s = input("Enter your string: ")
words = s.strip().split()
for word in words:
    print(word)

The strip() method is applied to string s, returning a version with no leading or trailing whitespace. The split() method is then applied to the stripped string, returning a list of words. The for loop iterates over that list, printing each word on a separate line.

Now let's do something more substantial with lists: count the lines, words, and characters in a chunk of text. We measure characters with len(), count lines by splitting on newlines, and accumulate a word total by splitting each line and summing the lengths. Save this as paragraph_stats.py:

Code
"""Count the words, lines and characters in a chunk of text."""

gettysburg = """\
Four score and seven years ago our
fathers brought forth on this continent,
a new nation, conceived in Liberty, and
dedicated to the proposition that
all men are created equal.

Now we are engaged in a great civil war,
testing whether that nation, or
any nation so conceived and so dedicated,
can long endure. We are met on
a great battle-field of that war. We have
come to dedicate a portion of that
field, as a final resting place for those
who here gave their lives that that
nation might live. It is altogether
fitting and proper that we should do this."""

charct = len(gettysburg)

lines = gettysburg.split("\n")
linect = len(lines)

wordct = 0
for line in lines:
    words = line.split()
    wordct += len(words)

print("The text contains", linect, "lines,", wordct, "words, and", charct, "characters.")

Run it, and you should see:

Output
The text contains 16 lines, 102 words, and 557 characters.
Note Some operating systems may give different results: Unix records a newline as one character, while Windows records it as two.

Now let's extend the program to keep a count of word lengths, so we know how many one-letter, two-letter, three-letter words there are, and so on. Modify paragraph_stats.py as shown:

Code
"""Count the words, lines and characters in a chunk of text."""

gettysburg = """\
Four score and seven years ago our
fathers brought forth on this continent,
a new nation, conceived in Liberty, and
dedicated to the proposition that
all men are created equal.

Now we are engaged in a great civil war,
testing whether that nation, or
any nation so conceived and so dedicated,
can long endure. We are met on
a great battle-field of that war. We have
come to dedicate a portion of that
field, as a final resting place for those
who here gave their lives that that
nation might live. It is altogether
fitting and proper that we should do this."""

lengthct = [0]*20 # a list of 20 zeroes
charct = len(gettysburg)

lines = gettysburg.split("\n")
linect = len(lines)

wordct = 0
for line in lines:
    words = line.split()
    wordct += len(words)
    for word in words:
        lengthct[len(word)] += 1

print("The text contains", linect, "lines,", wordct, "words, and", charct, "characters.")
for i, ct in enumerate(lengthct):
    if ct:
        print("Length", i, ":", ct)

We begin by creating a list of twenty counts, all initialised to zero. The count of n-letter words will be stored in lengthct[n]; we assume no word will be longer than nineteen characters. Within the line-processing loop we've added an inner loop that iterates over the words: the length of each word is used as an index into lengthct, and that element is incremented by one. After processing the text, we print the word-length counts, omitting any lengths with a zero count.

With the same Gettysburg text, the output should look like this:

Output
The text contains 16 lines, 102 words, and 557 characters.
Length 1 : 5
Length 2 : 19
Length 3 : 19
Length 4 : 16
Length 5 : 15
Length 6 : 6
Length 7 : 12
Length 8 : 2
Length 9 : 3
Length 10 : 3
Length 11 : 1
Length 12 : 1

Experiment further. Modify the text so it contains a word of twenty characters or more (like "deinstitutionalizing"). What happens when you run the program? How could you fix it? Can you think of a way to count individual words, to see how many times each one is used? With sequences alone this is possible, though not particularly easy.

Modern Python The enumerate(lengthct) call in the final loop is the idiomatic way to iterate over a sequence while also tracking the index. It's cleaner than a manual counter like i = 0; i += 1 or an index-based range(len(...)) loop.
Modern Python Counting word frequencies—the problem posed at the end of the section—becomes straightforward with a dict (or collections.Counter). We'll get to those in the next lesson.
It Slices, It Dices…

You've learned quite a bit about Python's sequence types and just how useful they can be. Next, we'll check out Python's mapping types.

Phew. This isn't easy, but you're doing really well. Keep it up, and I'll see you in the next lesson!