Level 2 · The Toolbox

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?

🪤 The one-item tuple

(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.

Exercise 1

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.8
Exercise 2

Tuple or list?

For each, say which you would choose and why.

  1. The RGB values of a colour.
  2. The names of everyone who has signed up to a newsletter.
  3. A row read from a spreadsheet.
  4. The days of the week.
  5. The cards currently in a player's hand.
Reveal solution
  1. Tuple. Exactly three parts, fixed meaning, will not grow.
  2. List. The whole point is that people join and leave.
  3. Tuple (or a named tuple). A row has a fixed shape.
  4. Tuple. There have been seven since Babylon and there will be seven tomorrow.
  5. 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?

Exercise 3

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 9

for title, *ratings in data unpacks and stars in one move. This is the kind of line that makes people say Python is elegant.

+100 XP