Chapter 12 · pp. 137–146
Tuples
A tuple is a sequence like a list, except that it cannot be changed after it is made. This chapter shows how to create and use tuples, how they let a function hand back several values, and how they team up with lists and dictionaries. It also covers gather and scatter for flexible arguments, and a tool for finding "shape" errors in nested data.
What a tuple is and how to make one
A tuple holds values of any type, and each value is found by an integer index. In that way it resembles a list. The key difference is that a tuple is immutable: once built, its elements cannot be changed in place (p. 137).
You write a tuple as values separated by commas. Parentheses are common but not required, so t = ('a', 'b', 'c', 'd', 'e') and t = 'a', 'b', 'c', 'd', 'e' mean the same thing (p. 137).
One-element tuples. The comma is what makes a tuple, not the parentheses. t1 = 'a', is a tuple, but t2 = ('a') is just a string with parentheses around it. If you forget the comma, Python reports type str, not tuple (p. 137).
The tuple() function. Calling tuple() with no argument gives the empty tuple (). Given a sequence, it copies the elements into a tuple: tuple('lupins') gives ('l', 'u', 'p', 'i', 'n', 's'). Because tuple is a built-in name, do not use it as a variable name (pp. 137–138).
Using tuples: indexing, slicing, and no item assignment
Most list operators work on tuples. With t = ('a', 'b', 'c', 'd', 'e'), t[0] is 'a' and the slice t[1:3] is ('b', 'c') (p. 138).
Trying t[0] = 'A' raises a TypeError, because tuples do not allow item assignment. To get a changed version, build a new tuple and point the variable at it: t = ('A',) + t[1:]. The old tuple is not modified. The name t now refers to a brand-new tuple, ('A', 'b', 'c', 'd', 'e') (p. 138). Notice the comma in ('A',), which is needed so that it is a tuple and can be joined with another tuple.
Comparing tuples
Relational operators such as < work on tuples. Python compares the first elements. If they are equal, it moves to the second elements, and so on. It stops at the first pair that differs, and that pair decides the answer. Later elements are ignored, even if they are huge (p. 138).
Example: (0, 1, 2000000) < (0, 3, 4) is True. The first elements tie at 0, then 1 < 3 settles it. The 2000000 is never looked at.
Tuple assignment and swapping
In tuple assignment, a tuple of variables on the left receives the values from the right, each going to its matching variable (pp. 138–139). The classic use is a swap:
a, b = b, a
Without this you would need a temporary variable and three lines. It works because every expression on the right side is evaluated before any assignment is made. So the old value of a is safely remembered while a is being overwritten (p. 139).
The counts must match. a, b = 1, 2, 3 raises a ValueError (too many values to unpack). The right side can be any sequence, not only a tuple. For example, 'monty@python.org'.split('@') returns a two-item list, and uname, domain = addr.split('@') puts 'monty' in uname and 'python.org' in domain (p. 139).
Tuples as return values
Strictly, a function returns just one value. If that value is a tuple, though, the effect is several values at once (p. 139). This is handy when two results are computed together. The built-in divmod(7, 3) returns (2, 1), the quotient and the remainder. You can keep the tuple, or unpack it right away: quot, rem = divmod(7, 3) gives quot = 2 and rem = 1. Doing it in one call is more efficient than computing 7 // 3 and 7 % 3 separately.
You can write your own version of this idea:
def min_max(t): return min(t), max(t)
The comma builds a tuple of the smallest and largest elements (p. 139).
Gather and scatter
Gather. A parameter whose name starts with * collects any number of arguments into a tuple. With def printall(*args): print(args), the call printall(1, 2.0, '3') prints (1, 2.0, '3'). The name args is only a convention (p. 140).
Scatter. The * operator in a call does the reverse: it spreads a sequence into separate arguments. divmod needs exactly two arguments, so divmod(t) with t = (7, 3) fails with a TypeError. Writing divmod(*t) works and returns (2, 1) (p. 140).
Many built-ins take a variable number of arguments. max(1, 2, 3) works and gives 3, but sum(1, 2, 3) fails because sum expects a single sequence (plus an optional start value) (p. 140).
zip and enumerate
zip takes two or more sequences and pairs up the elements in matching positions. It returns a zip object, which is an iterator. You can loop over it, but you cannot use an index on it. Wrap it in list() if you want list behavior (pp. 140–141).
list(zip('abc', [0, 1, 2])) gives [('a', 0), ('b', 1), ('c', 2)].
- If the sequences differ in length, the result is as long as the shortest one.
zip('Anne', 'Elk') gives only 3 pairs: ('A','E'), ('n','l'), ('n','k') (p. 141).
Tuple assignment works in a for loop over a list of tuples. In for letter, number in t:, each tuple is split into the two variables on each pass (p. 141).
Putting zip, for, and tuple assignment together lets you walk through several sequences at the same time. The function has_match(t1, t2) uses for x, y in zip(t1, t2) and returns True as soon as x == y at some position. If the loop ends with no match, it returns False (p. 141).
When you need both the position and the value, use enumerate. In for index, element in enumerate('abc'), you get (0, 'a'), (1, 'b'), (2, 'c'). The index starts at 0 (p. 141).
Dictionaries and tuples
The two types convert into each other (p. 142).
- Dictionary to tuples.
d.items() gives key-value pairs as tuples, in no particular order. A common loop is for key, value in d.items():.
- Tuples to dictionary.
dict() accepts a list of tuples and builds a dictionary. Combined with zip, dict(zip('abc', range(3))) is a short way to build {'a': 0, 'b': 1, 'c': 2}. The update method also accepts a list of tuples and adds them to an existing dictionary.
- Tuples as keys. Keys must be immutable, so tuples can be keys but lists cannot. A phone directory might use
directory[last, first] = number. The bracket contents are a tuple. You can loop with for last, first in directory: to unpack each key.
Choosing between sequences
All these ideas also work with lists of lists, tuples of tuples, and so on, so it helps to think of "sequences of sequences" (p. 143).
Strings are the most limited: they can hold only characters and are immutable. If you must change characters, use a list of characters. Lists are more common than tuples because they are mutable. Tuples are better in three cases (p. 143):
- A
return statement, where a tuple is simpler to write.
- A dictionary key, which must be immutable.
- An argument passed to a function, since a tuple cannot be changed by the function, which avoids surprises from aliasing.
Tuples have no in-place methods like sort or reverse. Instead, sorted(seq) returns a new sorted list from any sequence, and reversed(seq) returns an iterator that goes through the sequence backward (p. 143).
Debugging shape errors
Nested structures, such as a list of tuples or a dictionary with tuple keys, can cause shape errors: the data has the wrong type, size, or nesting. For example, a function that expects a list holding one integer breaks if given a bare integer (p. 144).
The structshape function summarizes a structure's shape as a string. structshape([1, 2, 3]) returns 'list of 3 int'. For [[1,2],[3,4],[5,6]] it returns 'list of 3 list of 2 int', showing the nesting at a glance (p. 144). Printing the shape is a quick way to see whether your data looks the way you think it does.
Check yourself
- Why is
t = ('a') not a tuple, and how do you fix it?
- After
t = ('a', 'b', 'c'), how can you get a tuple with 'z' in place of 'a', given that t[0] = 'z' fails?
- Is
(1, 5, 0) < (1, 2, 99) true or false? Explain how Python decides.
- What is the difference between
divmod(t) and divmod(*t) when t = (7, 3)?
- What does
list(zip('abcd', [1, 2])) produce, and why would a list not work as a dictionary key where a tuple does?