Lists aren’t the only kind of ordered sequence in Python. You’ve already learned about one of the others: strings (see Chapter 4, Working with Text). Formally, a string is an immutable sequence of characters. The characters in a string are ordered and a string can be indexed and sliced like a list to create new strings:
| >>> rock = 'anthracite' |
| >>> rock[9] |
| 'e' |
| >>> rock[0:3] |
| 'ant' |
| >>> rock[-5:] |
| 'acite' |
| >>> for character in rock[:5]: |
| ... print(character) |
| ... |
| |
| a |
| n |
| t |
| h |
| r |
Python also has an immutable sequence type called a tuple. Tuples are written using parentheses instead of brackets; like strings and lists, they can be subscripted, sliced, and looped over:
| >>> bases = ('A', 'C', 'G', 'T') |
| >>> for base in bases: |
| ... print(base) |
| ... |
| A |
| C |
| G |
| T |
There’s one small catch: although () represents the empty tuple, a tuple with one element is not written as (x) but as (x,) (with a trailing comma). This is done to avoid ambiguity. If the trailing comma weren’t required, (5 + 3) could mean either 8 (under the normal rules of arithmetic) or the tuple containing only the value 8:
| >>> (8) |
| 8 |
| >>> type((8)) |
| <class 'int'> |
| >>> (8,) |
| (8,) |
| >>> type((8,)) |
| <class 'tuple'> |
| >>> (5 + 3) |
| 8 |
| >>> (5 + 3,) |
| (8,) |
Unlike lists, once a tuple is created, it cannot be mutated:
| >>> life = (['Canada', 76.5], ['United States', 75.5], ['Mexico', 72.0]) |
| >>> life[0] = life[1] |
| Traceback (most recent call last): |
| File "<stdin>", line 1, in ? |
| TypeError: object does not support item assignment |
However, the objects inside tuples can still be mutated:
| >>> life = (['Canada', 76.5], ['United States', 75.5], ['Mexico', 72.0]) |
| >>> life[0][1] = 80.0 |
| >>> life |
| (['Canada', 80.0], ['United States', 75.5], ['Mexico', 72.0]) |
Here is an example that explores what is mutable and what isn’t. We’ll build the same tuple as in the previous example, but we’ll do it in steps. First let’s create three lists:
| >>> canada = ['Canada', 76.5] |
| >>> usa = ['United States', 75.5] |
| >>> mexico = ['Mexico', 72.0] |
That builds this memory model:
We’ll create a tuple using those variables:
| >>> life = (canada, usa, mexico) |
Notice that none of the four variables know about the others, and that the tuple object contains three references, one for each of the country lists.
Now let’s change what variable mexico refers to:
| >>> mexico = ['Mexico', 72.5] |
| >>> life |
| (['Canada', 76.5], ['United States', 75.5], ['Mexico', 72.0]) |
Notice that the tuple that variable life refers to hasn’t changed. Here’s the new picture as shown.
life[0] will always refer to the same list object—we can’t change the memory address stored in life[0]—but we can mutate that list object. And because variable canada also refers to that list, it sees the mutation:
| >>> life[0][1] = 80.0 |
| >>> canada |
| ['Canada', 80.0] |
We hope that it is clear how essential it is to thoroughly understand variables and references and how collections contain references to objects and not to variables.
You can assign to multiple variables in the same assignment statement:
| >>> (x, y) = (10, 20) |
| >>> x |
| 10 |
| >>> y |
| 20 |
As with a normal assignment statement (see Assignment Statement), Python first evaluates all expressions on the right side of the = symbol, and then it assigns those values to the variables on the left side.
Python uses the comma as a tuple constructor, so we can leave off the parentheses:
| >>> 10, 20 |
| (10, 20) |
| >>> x, y = 10, 20 |
| >>> x |
| 10 |
| >>> y |
| 20 |
In fact, multiple assignment will work with lists and sets as well. Python will happily pull apart information out of any collection:
| >>> [[w, x], [[y], z]] = [{10, 20}, [(30,), 40]] |
| >>> w |
| 10 |
| >>> x |
| 20 |
| >>> y |
| 30 |
| >>> z |
| 40 |
Any depth of nesting will work as long as the structure on the right can be translated into the structure on the left.
One of the most common uses of multiple assignment is to swap the values of two variables:
| >>> s1 = 'first' |
| >>> s2 = 'second' |
| >>> s1, s2 = s2, s1 |
| >>> s1 |
| 'second' |
| >>> s2 |
| 'first' |
This works because the expressions on the right side of the operator = are evaluated before assigning to the variables on the left side.