Tuples and Unpacking 🔒
A tuple is a list that has been set in resin. That sounds like a downside. It is the reason tuples show up everywhere in real Python.
Round brackets instead of square
point = (3, 7)
colours = ("red", "green", "blue")
print(point, colours)
print(point[0], colours[-1])
print(len(colours))
(3, 7) ('red', 'green', 'blue')
3 blue
3
Everything you can read from a list works. Everything that would change it does not:
point = (3, 7)
point[0] = 10
TypeError: 'tuple' object does not support item assignment
Why would you want that?
- It documents intent. A tuple says "these belong together and this set will not change": a coordinate, an RGB colour, a database row.
- It cannot be broken by accident. Hand a list to a function and the function might append to it. Hand a tuple and it cannot.
- It can be a dictionary key. Lists cannot (Lesson 13 explains why).
- It is slightly smaller and faster. True, and almost never the reason to choose one.
(5) is just the number 5 in brackets. A one-item tuple needs a trailing comma: (5,). It looks like a typo, it is required, and it has confused every Python programmer who ever lived at least once.
not_a_tuple = (5)
actually_a_tuple = (5,)
print(type(not_a_tuple))
print(type(actually_a_tuple))
<class 'int'>
<class 'tuple'>
Unpacking: the feature you will use every day
point = (3, 7)
x, y = point
print(x, y)
# the brackets are optional, which is why this works
name, role, age = "Guybrush", "pirate", 24
print(f"{name} the {role}, aged {age}")
# and why swapping is one line
a, b = 1, 2
a, b = b, a
print(a, b)
3 7
Guybrush the pirate, aged 24
2 1
The number of names on the left must match the number of values on the right, or Python
raises ValueError: too many values to unpack. Unless you use a star:
scores = [95, 88, 72, 61, 40]
best, *rest = scores
print(best, rest)
first, *middle, last = scores
print(first, middle, last)
95 [88, 72, 61, 40]
95 [88, 72, 61] 40
The starred name soaks up everything left over, and always becomes a list. There can be at most one of them, for the obvious reason.
Returning several things at once
def split_name(full_name):
parts = full_name.split()
return parts[0], parts[-1]
first, last = split_name("Guybrush Ulysses Threepwood")
print(first, last)
both = split_name("Elaine Marley")
print(both, type(both))
Guybrush Threepwood
('Elaine', 'Marley') <class 'tuple'>
A function can only return one object, but that object can be a tuple, so in practice Python functions return as many values as they like. Notice you did not have to write any brackets: a bare comma-separated list of values is a tuple. This is the single most common use of tuples in real code.
Tuples in loops
crew = [
("Guybrush", "pirate", 24),
("Elaine", "governor", 28),
("Otis", "prisoner", 41),
]
for name, role, age in crew:
print(f"{name:10} {role:10} {age}")
Guybrush pirate 24
Elaine governor 28
Otis prisoner 41
The loop unpacks each tuple automatically. This is exactly how enumerate
and zip from Lesson 9 work: they hand you tuples, and you unpack them in
the for line without thinking about it.
Named tuples, when positions get confusing
from collections import namedtuple
Pirate = namedtuple("Pirate", ["name", "role", "insults"])
guy = Pirate("Guybrush", "pirate", 7)
print(guy)
print(guy.name, guy.insults)
print(guy[0]) # still a tuple underneath
name, role, insults = guy # still unpacks
print(role)
Pirate(name='Guybrush', role='pirate', insults=7)
Guybrush 7
Guybrush
pirate
guy.name beats guy[0] the moment you have more than two
fields. For anything more elaborate, Lesson 33's dataclass is the modern
answer, but named tuples are perfect when you want something immutable and light.
Min, max and average in one function
Write a function that takes a list of numbers and returns three values. Call it and unpack the result.
Reveal solution
def summarise(numbers):
return min(numbers), max(numbers), sum(numbers) / len(numbers)
lowest, highest, average = summarise([88, 42, 95, 61, 73])
print(f"Lowest: {lowest}")
print(f"Highest: {highest}")
print(f"Average: {average:.1f}")
Lowest: 42
Highest: 95
Average: 71.8Tuple or list?
For each, say which you would choose and why.
- The RGB values of a colour.
- The names of everyone who has signed up to a newsletter.
- A row read from a spreadsheet.
- The days of the week.
- The cards currently in a player's hand.
Reveal solution
- Tuple. Exactly three parts, fixed meaning, will not grow.
- List. The whole point is that people join and leave.
- Tuple (or a named tuple). A row has a fixed shape.
- Tuple. There have been seven since Babylon and there will be seven tomorrow.
- List. Cards are drawn and played constantly.
The question that decides it is almost always: does the number of items change during the program's life?
Unpack the mess
From this data, print each film's title and its highest rating, using unpacking rather than indexes.
data = [
("Monkey Island", 9, 8, 10),
("Disco Elysium", 10, 10, 9),
("Grim Fandango", 9, 9, 9),
]Reveal solution
data = [
("Monkey Island", 9, 8, 10),
("Disco Elysium", 10, 10, 9),
("Grim Fandango", 9, 9, 9),
]
for title, *ratings in data:
print(f"{title:15} best score {max(ratings)}")
Monkey Island best score 10
Disco Elysium best score 10
Grim Fandango best score 9for title, *ratings in data unpacks and stars in one move. This is the kind of line that makes people say Python is elegant.