Topic 4.3
Tuples and Unpacking
In one line
A tuple is an ordered collection that cannot be changed after it is created, written with round brackets. Tuples are the natural way to bundle a few related values, and unpacking lets you split a tuple (or any sequence) into separate variables in one line.
Think of it like this
A map coordinate such as latitude and longitude, or a name written on a printed ID card. Once it is printed, you don't edit it; if the data changes you print a new card. A tuple is a list that is locked: it keeps the order, it allows duplicates, you can read items by index, but you cannot add, remove or replace anything.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Tuple
- An ordered collection that cannot be changed after it is created. Written with round brackets.
- Immutable
- Cannot be changed after creation. Numbers, strings and tuples are immutable.
- Packing
- Putting several values into one tuple:
row = 1, 2, 3. - Unpacking
- Splitting a sequence into separate variables:
a, b, c = row. - Star unpacking
- Using
*nameto collect the leftover items into a list. - Record
- A bundle of related values, each with its own meaning, such as (name, age, city).
- Hashable
- Has a fixed value that Python can turn into a number for fast lookup, so it can be a dictionary key. Immutable tuples of hashable items are hashable.
- Named tuple
- A tuple whose positions also have names, made with
collections.namedtuple.
Step by step
01Make a tuple and read it
A tuple reads like a list: indexes, negative indexes, slices, len, in, count and index all work. What is missing is anything that changes it, so there is no append, remove or sort on a tuple.
point = (3, 4)
print(point[0]) # 3
print(point[-1]) # 4
print(len(point)) # 2
print(point + (5,)) # (3, 4, 5) a NEW tuple
print(type((5)), type((5,)))02The comma makes the tuple
Brackets are only needed to avoid confusion. x = 5, creates a one-item tuple. x = (5) is the number 5 with pointless brackets. The same rule explains print(1, 2): that is two separate values handed to print, not a tuple, because the brackets there belong to the call.
03Immutable means the slots are fixed
Assigning to a slot fails. Appending to a list inside a tuple works, because that does not change which object the slot points at. Immutability protects the tuple's structure, not the objects it refers to.
t = (1, [2, 3])
t[1].append(4)
print(t) # (1, [2, 3, 4])
# t[0] = 9 # TypeError: 'tuple' object does not support item assignment04Packing and unpacking
Unpacking assigns by position. The left side needs the same number of names as there are items. Underscore _ is a common name for a value you don't need.
row = "Asha", 21, "Delhi" # packing
name, age, city = row # unpacking
print(name, age, city)
name, _, city = row # ignore the age
print(name, city)05Swap with a tuple
In other languages swapping needs a third variable. In Python the right side b, a is evaluated completely first, into a tuple, before any name on the left is changed.
a = 1
b = 2
a, b = b, a
print(a, b) # 2 106Star unpacking
*rest collects the items that the plain names did not take, and always gives a list (even when the source was a tuple). It can sit at the start, middle or end. It is empty ([]) when there is nothing left.
first, *middle, last = [1, 2, 3, 4, 5]
print(first, middle, last) # 1 [2, 3, 4] 5
head, *tail = "abc"
print(head, tail) # a ['b', 'c']07Tuples as records and keys
A tuple of numbers or strings can be a dictionary key, which a list cannot. That is ideal for a grid position, (row, column), or a route, (from_city, to_city). For records with many fields, a named tuple adds names to the positions. The curly brackets below make a dictionary (Topic 4.4): a lookup table where a key finds a value. The first line, from collections import namedtuple, fetches a ready-made tool; Topic 6.1 teaches import, so copy it as it is for now.
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p[1]) # 3 4
distance = {("Delhi", "Mumbai"): 1400}
print(distance[("Delhi", "Mumbai")])Try it yourself
- 1
Predict the types
Before running the first example, decide what
type((5))andtype((5,))are. Then remove the comma insingle = (5,)and run again. - 2
Unpack a longer record
In the first example, change
rowto"Asha", 21, "Delhi", "India". Run it. Then fix it by adding a fourth name, or by using*extraat the end of the left side.
Code & diagrams
Add a fourth value to row and run it: what error do you get when unpacking into three names? (`type(x).__name__` just gives the name of x's type as text.)
Expected output
point: (3, 4) | first: 3 | length: 2
single: tuple | not_a_tuple: int
Asha is 21 and lives in Delhi
swapped: 2 1
first: 1 | middle: [2, 3, 4] | last: 5
the list inside changed: (1, [2, 3, 4])The last line shows that tuples sort item by item: first items first, and the second items break ties. `from collections import namedtuple` fetches a ready-made tool (Topic 6.1 teaches import).
Expected output
Point(x=3, y=4)
x = 3 | y = 4 | p[1] = 4
unpacked: 3 4
Delhi to Mumbai: 1400 km
at (2, 3): treasure
[(1, 'z'), (2, 'a'), (2, 'b')]Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Change a tuple
Try to replace an item of a tuple.
point = (3, 4)
point[0] = 10Break #2
Unpack with the wrong number of names
Give three values to two names.
a, b = (1, 2, 3)Myth vs fact
Myth
Tuples are just slow lists with fewer features.
Fact
They are lighter and slightly faster to create, but the real value is meaning: a tuple says "this group of values is one fixed thing". They can also be dictionary keys.
Myth
The round brackets make a tuple.
Fact
The commas make it. x = 1, 2 is a tuple. The one exception is the empty tuple (). A single item needs the comma: (1,).
Myth
Everything inside a tuple is frozen.
Fact
Only the tuple's own slots are fixed. A list inside a tuple can still be changed.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
CPython can store constant tuples in the compiled code and reuse them, and creating a small tuple is cheaper than creating a list, because a tuple never needs spare room to grow. The difference is small, so choose by meaning first. The swap
a, b = b, adoes not even build a tuple in CPython: the compiler emits a direct stack swap (dis.disshows it, Topic 13.3), and the meaning is still the "evaluate the right side first" rule, so you can think in tuples. - ▸
A tuple is hashable only if all its items are.
hash((1, [2]))raisesTypeError: unhashable type: 'list'. This is why(row, col)pairs of ints work as dictionary keys and("a", [1])does not. - ▸
A famous puzzle:
t = (1, [2]); t[1] += [3]raisesTypeErrorand the list still becomes[2, 3]. The in-place addition works on the list, then Python tries to store the result back into the tuple slot and fails. Moral: don't use+=on a list that lives inside a tuple. - ▸
Records with many fields are better as a
dataclass(Topic 8.8) or a Pydantic model (Topic 15.4) than as a long tuple: names catch mistakes that positions hide. Libraries often return tuples (such as a shape(rows, columns)in NumPy), and you unpack them in one line.
Remember this
- 1
You write a tuple with round brackets and commas:
point = (3, 4). The commas make the tuple, not the brackets:point = 3, 4is the same. An empty tuple is(). The one-item tuple needs a trailing comma:(5,)is a tuple with one item, while(5)is just the number 5 in brackets. That small comma is a classic stumbling block. - 2
Tuples are immutable, which means they cannot be changed after they are made:
point[0] = 10raisesTypeError. A tuple, like a list, has numbered slots (positions that each point at a value). "Locked" applies only to the tuple's own slots. If a slot holds a list, that list can still be changed:t = (1, [2, 3])thent[1].append(4)works. The tuple still points at the same list; the list's contents moved. - 3
Packing means putting values into a tuple:
row = "Asha", 21, "Delhi". Unpacking is the reverse:name, age, city = rowgives each variable one item. The number of names on the left must match the number of items, or Python raisesValueError. Unpacking works with any sequence (an ordered collection such as a list, a string or a tuple). It is also howfor index, item in enumerate(items)gets two values per loop (Topic 2.6), and howfor key, value in d.items()does the same for dictionaries (Topic 4.4). - 4
Unpacking is also how you swap two variables without a helper:
a, b = b, a. Python first builds the tuple(b, a)on the right and then unpacks it, so nothing is lost. A*nameon the left collects "all the rest" into a list:first, *middle, last = [1, 2, 3, 4, 5]givesfirst = 1,middle = [2, 3, 4],last = 5. Only one starred name is allowed in one unpacking. - 5
Use a tuple for a record of different things that belong together (name, age, city) or a fixed pair like a coordinate. Use a list for a collection of the same kind of thing that may grow. Functions return "two values" by returning a tuple, which you will see in Phase 5. Because a tuple of numbers or strings is immutable, it can be a dictionary key or a set member; a list cannot. (Dictionaries are Topic 4.4 and sets are Topic 4.6. Topic 4.5 explains why a list cannot be a key.)
- 6
A named tuple gives each position a name, so
p.xreads better thanp[0]:Point = namedtuple("Point", ["x", "y"])from thecollectionsmodule, a ready-made toolbox that comes with Python (Topic 4.10 covers it fully, and Topic 8.8 showsdataclassas a richer alternative). It is still a tuple underneath and can be unpacked like one.
Explain it without notes
What is the difference between a list and a tuple, and when would you choose each?
Why is (5) not a tuple, and how do you write one?
How does a, b = b, a swap two variables?
What does first, *rest = items do?
Is every tuple usable as a dictionary key? What happens with t = (1, [2, 3])?
Practice
Make a tuple birthday = (15, "August", 2010). Unpack it into day, month, year and print 15 August 2010 using those variables.
Given a = 10 and b = 20, swap them without a third variable and print a = 20, b = 10.
Given scores = [60, 70, 80, 90, 100], use star unpacking to get the lowest, the highest and a list of the middle scores. Print all three.
Trade-offs
- ↔
Immutability buys safety and dictionary-key power, and costs convenience. Nobody can change a tuple by accident, and it can be hashed. But to "change" it you build a new one.
- ↔
Positions are compact but cryptic.
row[2]means nothing until you remember what column 2 was. Named tuples, dataclasses or dictionaries cost a little more and read much better as the record grows.
Done when you can
Done when you can write a tuple, including a one-item tuple, and say what the comma does.
Done when you can unpack a tuple into variables and explain the
ValueErrorfor a wrong count.Done when you can swap two variables and use
first, *rest = items.Done when you can explain why a tuple of ints can be a dictionary key and a list cannot.
Done when you can explain why a list inside a tuple can still change.