Python tuples: creation, immutability, packing, unpacking and tuple vs list
Advertisement
A tuple is an ordered, unchangeable sequence. It looks like a list written with round brackets instead of square ones, and the difference in behaviour is small but important: once a tuple is created, you can't add, remove or replace its items. This post covers how to create and use tuples, what "immutable" really guarantees (and what it doesn't), the very useful pack and unpack syntax, and how to choose between a tuple and a list. All outputs were produced on a real interpreter.
Creating tuples
t = (10, 20, 30, 20)
print(t, t[0], t[-1], t[1:3]) # (10, 20, 30, 20) 10 20 (20, 30)
print(tuple([1, 2]), tuple("abc"), ()) # (1, 2) ('a', 'b', 'c') ()
You build a tuple with commas; the brackets are mostly for readability. That leads to the classic trap, the one-item tuple:
print((5,), type((5)), type((5,)))
# (5,) <class 'int'> <class 'tuple'>
(5) is just the number 5 in brackets. The comma makes the tuple: (5,) or even 5,. An empty tuple is ().
Other ways to get a tuple: tuple(iterable) converts any sequence (a list, a string, a range), and a function that does return 1, 9 returns a tuple.
def mm():
return 1, 9
print(mm(), type(mm())) # (1, 9) <class 'tuple'>
Accessing items
Tuples use the same indexing and slicing as lists and strings: t[0] is the first item, t[-1] the last, t[1:3] a slice (returning a new tuple), and t[::-1] the reversed tuple. Looping works too:
for i, (n, v) in enumerate([("a", 1), ("b", 2)]):
print(i, n, v)
# 0 a 1
# 1 b 2
Useful built-ins and methods:
t = (10, 20, 30, 20)
print(t.count(20), t.index(20), len(t), max(t), sum(t)) # 2 1 4 30 80
print(sorted((3, 1, 2)), list(reversed((1, 2, 3)))) # [1, 2, 3] [3, 2, 1]
A tuple has only two methods, count() and index(), because it can't change. sorted() always returns a list.
Immutability
Trying to change a tuple fails:
try:
t[0] = 1
except TypeError as e:
print(e) # 'tuple' object does not support item assignment
You can still create new tuples from old ones, using + (concatenation) and * (repetition):
print(t + (1,), (1, 2) * 2) # (10, 20, 30, 20, 1) (1, 2, 1, 2)
To "change" a tuple, build a new one and rebind the name: t = t[:1] + (99,) + t[2:].
The catch: shallow immutability
A tuple is immutable in its structure: the set of objects it holds can't change. But if one of those objects is mutable, such as a list, that object can still change:
lst = [1, 2]
tt = (lst, 3)
lst.append(9)
print(tt) # ([1, 2, 9], 3)
The tuple still holds the same list object; the list itself changed. This is also why a tuple is only hashable when everything inside it is hashable:
print(hash((1, 2)) == hash((1, 2))) # True
try:
hash(([1], 2))
except TypeError as e:
print(e) # unhashable type: 'list'
Hashability lets tuples be dictionary keys and set members, which lists cannot be:
d = {(1, 2): "point"}
print(d[(1, 2)]) # point
Coordinates, dates as (year, month, day) and composite keys are natural uses.
Comparison and membership
Tuples compare item by item from the left, like words in a dictionary:
print((1, 2, 3) < (1, 2, 4), (1, 2) == (1, 2), 20 in t) # True True True
This is what makes sorting by several fields easy: sorted(people, key=lambda p: (p["city"], p["name"])) sorts by city and then by name.
Packing and unpacking
Packing puts several values into one tuple; unpacking spreads a tuple into separate names.
point = 3, 4 # packing
x, y = point # unpacking
a, b, c = (1, 2, 3)
print(a, b, c) # 1 2 3
The number of names must match the number of items, unless you use * to collect the rest:
first, *mid, last = range(6)
print(first, mid, last) # 0 [1, 2, 3, 4] 5
The starred name always becomes a list. Unpacking powers the idioms you see everywhere: swapping (a, b = b, a), looping over pairs (for k, v in d.items()), and returning several values from a function.
There is no tuple comprehension
People often try (x*x for x in range(4)) hoping for a tuple. That is a generator expression, not a tuple:
g = (x * x for x in range(3))
print(g.__class__.__name__, tuple(g)) # generator (0, 1, 4)
print(tuple(x * x for x in range(4))) # (0, 1, 4, 9)
To get a tuple, wrap the generator in tuple(...). A generator produces its values one at a time and can be used only once.
namedtuple: tuples with field names
When a tuple has several fields, plain indexes (p[1]) become hard to read. collections.namedtuple gives each position a name while keeping the tuple's behaviour:
from collections import namedtuple
P = namedtuple("P", "x y")
p = P(1, 2)
print(p, p.x, p[1], p._asdict())
# P(x=1, y=2) 1 2 {'x': 1, 'y': 2}
It is still immutable, still unpackable (x, y = p), and uses no more memory than a tuple.
Tuple versus list
| Tuple | List | |
|---|---|---|
| Syntax | (1, 2) |
[1, 2] |
| Changeable | no | yes |
| Methods | count, index |
many (append, sort, ...) |
| Can be a dict key | yes (if items are hashable) | no |
| Memory | slightly smaller | slightly larger |
| Typical use | fixed records, coordinates, return values | a collection that grows or changes |
import sys
print(sys.getsizeof((1, 2, 3)) < sys.getsizeof([1, 2, 3])) # True
A good rule: use a tuple when the position of an item carries meaning ((name, age, city)) and the collection is a fixed record; use a list for a sequence of similar things that may change. Tuples also communicate intent: a reader knows that nothing will modify the data.
Where tuples appear without you asking
You use tuples all the time without writing brackets. A function that returns two values returns a tuple. dict.items() yields (key, value) pairs, and enumerate() yields (index, item) pairs. The % string-formatting operator takes a tuple of values. Multiple assignment (a, b = 1, 2) packs the right side into a tuple first. When a function accepts *args, those extra arguments arrive in a tuple. Recognising these makes the language feel more consistent: lists are for collections you build up, tuples for small fixed groups that travel together.
A practical performance note: because a tuple never changes, Python can store it compactly and, for a tuple of constants written in your code, build it only once. The difference is small and should never be the only reason to choose a tuple; choose it for meaning and safety, and let the speed be a bonus.
Common mistakes
- Forgetting the comma in a one-item tuple.
- Expecting a tuple to be deeply unchangeable.
- Trying to sort a tuple in place: tuples have no
sort(); usesorted(). - Unpacking into the wrong number of names (
ValueError). - Calling the generator expression a "tuple comprehension".
Practice
Write a function that returns the minimum, maximum and average of a list as a tuple and unpack it at the call site. Then store ten (name, score) tuples in a list and sort them by score, highest first. Finally, create a namedtuple for a book (title, author, year) and print a sorted catalogue.
Continue
The previous topic is operators, and sets and strings are close relatives. The fundamentals post places tuples among the other types, and the Python cheat sheet has a quick comparison of lists, tuples, dicts and sets.