Session 15 of the Python Fundamentals series, covered in depth. Tuples are immutable, ordered sequences, and the session covers creating them with (a, b), the trailing-comma rule for a single element, packing and unpacking, multiple assignment, swapping in one line, unpacking tuples in for loops, returning several values from a function, and star unpacking with *rest. It explains why immutability matters, since it gives safe fixed records and lets a tuple serve as a dictionary key, and when to reach for a tuple rather than a list. The traps are that (x) is not a tuple, that reassigning t[0] raises a TypeError, and that unpacking the wrong number of values raises a ValueError. Every snippet and error message was executed and copied verbatim from CPython 3.12.
Subject: Python Fundamentals · 100 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
Python Fundamentals - Session 15
Fixed records, multiple values at once, and the trailing comma that trips everyone up
Objectives
Lists hold things that change. A tuple holds a fixed group of values that belong together. By the end you can:
(a, b) and read its items by index.a, b = 1, 2, and swap with a, b = b, a.for loop and return several values from a function.first, *rest = ... star unpacking.t[0] = ..., and unpacking the wrong count.Warm-up
Discussion prompt
Before we open Session 15 - Tuples & Unpacking: without looking back, what was the main idea of Session 14 - Capstone: Adventure Game (Part 2), and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
That session keeps state in a dictionary, adds an inventory and a score, makes decisions from that state, and builds real winning and losing endings, then polishes and extends the game.
Section
Part 1
Concept
A tuple stores several values in order, just like a list - but once made, it cannot be changed.
tuple — An ordered, immutable sequence of values, written with commas and usually round brackets: (3, 4). Immutable means you cannot add, remove, or replace items after it is built.
Counterexample
Discussion prompt
A tuple stores several values in order, just like a list - but once made, it cannot be changed.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Concept
It is the commas, not the brackets, that build a tuple. The () just group it for readability.
point = (3, 4) makes a tuple of two values. You read items by index with [ ], exactly like a list.
Analogy
Discussion prompt
Explain The comma is what makes a tuple by analogy to something with no Python Fundamentals in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
point = (3, 4) makes a tuple of two values. You read items by index with [ ], exactly like a list.
Ranking
Put in order
Put the moves of Make a tuple and read it into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. point is a single value that holds two numbers in order.
Worked example
point = (3, 4)
print(point)
print(point[0])
print(point[1])
print(type(point))Line 1 packs 3 and 4 into one tuple
Why: point is a single value that holds two numbers in order.
Index with [0] and [1] just like a list
Why: Indexing reads an item; it does not change the tuple.
Read the output
Why: Verified by execution: the whole tuple, then each item, then its type.
| expression | value |
|---|---|
| point | (3, 4) |
| point[0] | 3 |
| point[1] | 4 |
| type(point) | <class 'tuple'> |
Comparison
Comparison matrix
From Make a tuple and read it: refill the value column from what you know. The rest of the table is as it appeared.
| expression | value |
|---|---|
| point | (3, 4) |
| point[0] | 3 |
| point[1] | 4 |
| type(point) | <class 'tuple'> |
Intuition
Picture a shipping label already printed and sealed: a name and a date, side by side, in a fixed order.
You can read what is on the label all day, but you cannot cross out the name and write a new one. To change it, you print a fresh label. That is a tuple.
Explain it
Discussion prompt
Explain A tuple is a sealed label to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
Picture a shipping label already printed and sealed: a name and a date, side by side, in a fixed order.
Concept
For a one-item tuple you must add a comma: (5,). Without it, (5) is just the number 5 in ordinary brackets.
Remember: the comma makes the tuple, so a lone value with no comma is not a tuple at all.
Step zero
Discussion prompt
(5,) versus (5) — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: The trailing comma on line 1 builds a tuple
Answer:
Worked example
one = (5,)
not_a_tuple = (5)
print(one, type(one))
print(not_a_tuple, type(not_a_tuple))The trailing comma on line 1 builds a tuple
Why: (5,) is a one-item tuple; the comma is doing the work.
No comma on line 2 means plain brackets
Why: (5) is just 5 - the brackets only group, they do not make a tuple.
Read the output
Why: Verified by execution: one is a tuple, not_a_tuple is an int.
| name | value | type |
|---|---|---|
| one | (5,) | <class 'tuple'> |
| not_a_tuple | 5 | <class 'int'> |
Trade off
Comparison matrix
From (5,) versus (5): every row here is a choice with a cost. Fill the value column, then say which row you would actually pick and what you give up for it.
| name | value | type |
|---|---|---|
| one | (5,) | <class 'tuple'> |
| not_a_tuple | 5 | <class 'int'> |
Anomaly
Predict first
A student writes this, and it looks reasonable:
You mean a one-item tuple, but write plain brackets.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Without the comma, the brackets just group a number, so y is the int 5 - later code that expects a tuple will misbehave.
Add the trailing comma.
Why: Without the comma, the brackets just group a number, so y is the int 5 - later code that expects a tuple will misbehave.
Trap
You mean a one-item tuple, but write plain brackets.
y = (5)
print(y, type(y))(5) is not a tuple
Why: Without the comma, the brackets just group a number, so y is the int 5 - later code that expects a tuple will misbehave.
| you write | y is | type |
|---|---|---|
| (5) | 5 | <class 'int'> |
Add the trailing comma.
x = 5,
print(x, type(x))5, makes a one-item tuple
Why: The comma builds the tuple - brackets are optional. Real output: (5,) and <class 'tuple'>. Rule of thumb: a single trailing comma is the whole difference.
| you write | x is | type |
|---|---|---|
| 5, | (5,) | <class 'tuple'> |
Break the constraint
Discussion prompt
The rule this trap just fixed:
The comma builds the tuple - brackets are optional. Real output: (5,) and <class 'tuple'>. Rule of thumb: a single trailing comma is the whole difference.
Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?
Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.
Answer:
Without the comma, the brackets just group a number, so y is the int 5 - later code that expects a tuple will misbehave.
Section
Part 2
Concept
Write values separated by commas and Python packs them into a tuple - brackets optional.
packed = 1, 2, 3 and packed = (1, 2, 3) mean the same thing.
Concept
Put matching names on the left and Python unpacks the tuple, one value per name, in order.
unpacking — Assigning the items of a sequence to several variables at once: a, b, c = (1, 2, 3) sets a=1, b=2, c=3. The count on the left must match the count on the right.
Definition probe
Sort into buckets
Every line below is part of the definition of tuple or of unpacking — one or the other, never both. Put each where it belongs.
Ranking
Put in order
Put the moves of Pack, then unpack into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. No brackets needed - the commas pack 1, 2, 3 into one tuple.
Worked example
packed = 1, 2, 3
print(packed, type(packed))
a, b, c = packed
print(a, b, c)Line 1 packs three values
Why: No brackets needed - the commas pack 1, 2, 3 into one tuple.
Line 3 unpacks it into a, b, c
Why: Three names on the left take the three items in order.
Read the output
Why: Verified by execution: the tuple, then its three items spread across names.
| step | result |
|---|---|
| packed | (1, 2, 3) |
| a | 1 |
| b | 2 |
| c | 3 |
Error analysis
Annotate
Walk the callouts on Pack, then unpack. Each one is a place this is easy to get subtly wrong.
Concept
This is why a, b = 1, 2 works: the right side packs (1, 2), the left side unpacks it into a and b.
It is packing and unpacking happening together on a single line - a tidy way to set several variables at once.
Worked example
a, b = 1, 2
print(a, b)The right side packs (1, 2)
Why: 1, 2 becomes a temporary tuple.
The left side unpacks it
Why: Verified by execution: a is 1 and b is 2, so it prints 1 2.
| name | value |
|---|---|
| a | 1 |
| b | 2 |
Blank canvas
Draw it
Draw what Two variables at once just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Intuition
The tuple is a small stack of cards in a set order. Unpacking deals them out, one to each player named on the left.
If there are three cards and only two players - or two cards and three players - the deal fails. The counts have to line up. (You will see that failure as a ValueError shortly.)
Anomaly
Predict first
A student writes this, and it looks reasonable:
The count on the left does not match the tuple.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Python has nothing to give c, so it raises a ValueError instead of guessing.
Match the number of names to the number of values.
Why: Python has nothing to give c, so it raises a ValueError instead of guessing.
Trap
The count on the left does not match the tuple.
a, b, c = (1, 2)Three names, only two values
Why: Python has nothing to give c, so it raises a ValueError instead of guessing.
| case | result |
|---|---|
| a, b, c = (1, 2) | ValueError: not enough values to unpack (expected 3, got 2) |
| a, b = (1, 2, 3) | ValueError: too many values to unpack (expected 2) |
Match the number of names to the number of values.
a, b, c = (1, 2, 3)
print(a, b, c)Three names, three values
Why: Now every name gets exactly one item. Real output: 1 2 3. Rule of thumb: count both sides, or use a star target (coming up) when the count varies.
| name | value |
|---|---|
| a | 1 |
| b | 2 |
| c | 3 |
Pattern
Step through it
Step through Trap: wrong number of values to unpack one row at a time. What is driving the change, and what would the row after the last one be?
Section
Part 3
Concept
a, b = b, a swaps the two values in one line. The right side packs the current values first, then unpacks them into the flipped names.
No temporary variable needed - Python evaluates the whole right side before assigning anything.
Pattern
Predict first
The table runs: start | 10 | 3 · pack right side | 10 | 3
In Swap in one line, given the rows so far: what is the next one — the row where step is after swap?
Correct: after swap | 3 | 10
| step | a | b |
|---|---|---|
| start | 10 | 3 |
| pack right side | 10 | 3 |
| after swap | 3 | 10 |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. b is 3 and a is 10 right now, so it builds (3, 10) before assigning.
Worked example
a, b = 10, 3
print(a, b)
a, b = b, a
print(a, b)The right side packs (3, 10) first
Why: b is 3 and a is 10 right now, so it builds (3, 10) before assigning.
Then it unpacks into a, b
Why: Verified by execution: a becomes 3, b becomes 10. Prints 10 3 then 3 10.
| step | a | b |
|---|---|---|
| start | 10 | 3 |
| pack right side | 10 | 3 |
| after swap | 3 | 10 |
Fill the middle
Fill in the blanks
From The old way, for contrast — one line has had its right-hand side removed. Put it back.
a, b = 10, 3
temp = a
a = b
b = temp
print(a, b)
Why: b is what everything below it consumes, so the wrong expression here fails later and somewhere else. Without temp, setting a = b would lose the old a.
Worked example
a, b = 10, 3
temp = a
a = b
b = temp
print(a, b)Save a in temp before overwriting it
Why: Without temp, setting a = b would lose the old a. Three lines do what the swap does in one.
Read the output
Why: Verified by execution: same result, 3 10 - the tuple swap is just shorter and clearer.
| step | a | b | temp |
|---|---|---|---|
| start | 10 | 3 | - |
| temp = a | 10 | 3 | 10 |
| a = b | 3 | 3 | 10 |
| b = temp | 3 | 10 | 10 |
Discrimination
Sort into buckets
Sort these by a, from memory, without looking back at The old way, for contrast. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Section
Part 4
Concept
Data often comes as a list of tuples - each tuple a small record, like a name paired with a score.
Loop over it and unpack each tuple right in the for line: for name, score in scores:.
Step zero
Discussion prompt
Unpack each record in the loop — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Each item is a (name, score) tuple
Answer:
Worked example
scores = [("Ana", 88), ("Ben", 72), ("Cy", 95)]
for name, score in scores:
print(name, "scored", score)Each item is a (name, score) tuple
Why: The loop hands one tuple per pass.
name, score unpacks it each pass
Why: Two names catch the two items of the current tuple.
Trace the passes
Why: Verified by execution: one line printed per record.
| pass | name | score | prints |
|---|---|---|---|
| 1 | Ana | 88 | Ana scored 88 |
| 2 | Ben | 72 | Ben scored 72 |
| 3 | Cy | 95 | Cy scored 95 |
Worked example
colors = ["red", "green", "blue"]
for i, color in enumerate(colors):
print(i, color)enumerate pairs each item with its index
Why: Each pass yields a tuple like (0, 'red'), which i, color unpacks.
Trace the passes
Why: Verified by execution: index and value together, no manual counter needed.
| pass | i | color | prints |
|---|---|---|---|
| 1 | 0 | red | 0 red |
| 2 | 1 | green | 1 green |
| 3 | 2 | blue | 2 blue |
Pattern
Step through it
Step through enumerate gives (index, item) tuples one row at a time. What is driving the change, and what would the row after the last one be?
Worked example
prices = {"pen": 2, "pad": 5}
for item, price in prices.items():
print(item, price).items() gives a (key, value) tuple per pair
Why: item, price unpacks each one - the same loop-unpacking pattern from Session 8, now named.
Trace the passes
Why: Verified by execution: each key with its value.
| pass | item | price | prints |
|---|---|---|---|
| 1 | pen | 2 | pen 2 |
| 2 | pad | 5 | pad 5 |
Section
Part 5
Concept
return a, b packs the values into one tuple and hands it back - a function's clean way to answer with more than one result.
The caller can unpack it straight into names: low, high = min_max(nums).
Ranking
Put in order
Put the moves of Return the low and high together into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The comma makes one return value that holds both numbers.
Worked example
def min_max(nums):
return min(nums), max(nums)
low, high = min_max([4, 9, 1, 7])
print(low, high)
result = min_max([4, 9, 1, 7])
print(result, type(result))return min(nums), max(nums) packs a tuple
Why: The comma makes one return value that holds both numbers.
The caller unpacks it into low, high
Why: Two names catch the two returned items.
Or catch the whole tuple in one name
Why: Verified by execution: result is the tuple (1, 9). Prints 1 9, then (1, 9) <class 'tuple'>.
| step | value |
|---|---|
| min_max([4, 9, 1, 7]) | (1, 9) |
| low | 1 |
| high | 9 |
| result | (1, 9) |
Discrimination
Sort into buckets
Sort these by value, from memory, without looking back at Return the low and high together. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Worked example
def stats(nums):
return sum(nums), len(nums), sum(nums) / len(nums)
total, count, average = stats([10, 20, 30])
print(total, count, average)The function packs three results
Why: sum, count, and average all ride back in one tuple.
Unpack into three named variables
Why: Verified by execution: total 60, count 3, average 20.0. Prints 60 3 20.0.
| name | value |
|---|---|
| total | 60 |
| count | 3 |
| average | 20.0 |
Intuition
A function still hands back exactly one thing. A tuple lets that one thing be a small parcel with several items inside.
The caller can open the parcel item by item (unpack) or keep it sealed and pass it along (one name). Either way, one return, many values.
Section
Part 6
Concept
Put * before one name and it soaks up all the leftover items as a list: first, *rest = ....
This lets you unpack when you only know some of the positions - the star handles the variable middle or tail.
Worked example
first, *rest = [10, 20, 30, 40]
print(first)
print(rest)first takes the head, *rest takes the tail
Why: first is one value; the star collects everything left over.
The starred name is a list
Why: Verified by execution: first is 10, rest is [20, 30, 40] - note rest is a list, not a tuple.
| name | value | type |
|---|---|---|
| first | 10 | int |
| rest | [20, 30, 40] | list |
Comparison
Comparison matrix
From **first, rest: refill the type* column from what you know. The rest of the table is as it appeared.
| name | value | type |
|---|---|---|
| first | 10 | int |
| rest | [20, 30, 40] | list |
Worked example
*init, last = [10, 20, 30, 40]
print(init)
print(last)The star can sit at the front too
Why: *init grabs everything except the final item; last takes the tail.
Read the output
Why: Verified by execution: init is [10, 20, 30], last is 40.
| name | value |
|---|---|
| init | [10, 20, 30] |
| last | 40 |
Worked example
a, *mid, b = [1, 2, 3, 4, 5]
print(a, mid, b)Fixed ends, flexible middle
Why: a takes the first item, b the last, and *mid absorbs whatever is between.
Read the output
Why: Verified by execution: a is 1, b is 5, mid is [2, 3, 4]. Prints 1 [2, 3, 4] 5.
| name | value |
|---|---|
| a | 1 |
| mid | [2, 3, 4] |
| b | 5 |
Pattern
Step through it
Step through **a, mid, b* one row at a time. What is driving the change, and what would the row after the last one be?
Section
Part 7
Concept
A list lets you do scores[0] = 90. A tuple does not - trying it raises a TypeError.
That locked-down nature is the whole point: a tuple is a value you promise not to change.
Fill the middle
Fill in the blanks
From List changes, tuple record stays put — one line has had its right-hand side removed. Put it back.
scores = [88, 72, 95]
scores[0] = 90
print(scores)
record = ("Ana", "2010-05-14")
print(record[0], record[1])
Why: scores is what everything below it consumes, so the wrong expression here fails later and somewhere else. scores[0] = 90 replaces the first item in place.
Worked example
scores = [88, 72, 95]
scores[0] = 90
print(scores)
record = ("Ana", "2010-05-14")
print(record[0], record[1])The list allows item assignment
Why: scores[0] = 90 replaces the first item in place.
The tuple is read-only
Why: Verified by execution: you can read record[0] and record[1], but you cannot reassign them.
| expression | value |
|---|---|
| scores | [90, 72, 95] |
| record[0] | Ana |
| record[1] | 2010-05-14 |
Anomaly
Predict first
A student writes this, and it looks reasonable:
You reach for a list habit and assign into a tuple.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: A tuple cannot be edited in place, so Python raises a TypeError - the assignment never happens.
Build a fresh tuple instead of editing.
Why: A tuple cannot be edited in place, so Python raises a TypeError - the assignment never happens.
Trap
You reach for a list habit and assign into a tuple.
color = ("red", "green", "blue")
color[0] = "orange"Item assignment is not allowed
Why: A tuple cannot be edited in place, so Python raises a TypeError - the assignment never happens.
| line | result |
|---|---|
| color[0] = "orange" | TypeError: 'tuple' object does not support item assignment |
Build a fresh tuple instead of editing.
color = ("red", "green", "blue")
color = ("orange",) + color[1:]
print(color)Make a new tuple from the pieces
Why: Slicing and joining builds a new tuple; the old one is untouched. Real output: ('orange', 'green', 'blue'). If you truly need in-place edits, use a list.
| step | value |
|---|---|
| color[1:] | ('green', 'blue') |
| ('orange',) + color[1:] | ('orange', 'green', 'blue') |
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
point = (3, 4) makes a tuple of two values. You read items by index with [ ], exactly like a list.; Picture a shipping label already printed and sealed: a name and a date, side by side, in a fixed order.Concept
Because a tuple never changes, Python can use it as a dict key - handy for keying on a pair, like a grid coordinate.
A list cannot be a key (it can change), but a tuple of the same values can.
Worked example
distances = {}
distances[(0, 0)] = 0
distances[(1, 2)] = 5
print(distances[(1, 2)])
print(distances)Each key is a coordinate tuple
Why: The pair (1, 2) works as a single, unchangeable key.
Look one up by its tuple
Why: Verified by execution: distances[(1, 2)] is 5.
| key | value |
|---|---|
| (0, 0) | 0 |
| (1, 2) | 5 |
| lookup (1, 2) | 5 |
Pattern
Step through it
Step through A (row, col) coordinate as a key one row at a time. What is driving the change, and what would the row after the last one be?
Section
Part 8
Concept
Reach for a tuple when the group is a fixed record whose size and meaning do not change - a coordinate, a name-and-date, a function's several results.
Reach for a list when you will add, remove, or reorder items - a to-do list, a growing collection of scores.
Explain it
Discussion prompt
Explain When to use each to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
Reach for a tuple when the group is a fixed record whose size and meaning do not change - a coordinate, a name-and-date, a function's several results.
Fill the middle
Fill in the blanks
From Tuples support read-only operations — one line has had its right-hand side removed. Put it back.
t = (1, 2, 3)
print(len(t))
print(2 in t)
print(t + (4, 5))
print(t * 2)
Why: t is what everything below it consumes, so the wrong expression here fails later and somewhere else. These read or build new tuples; none of them change t.
Worked example
t = (1, 2, 3)
print(len(t))
print(2 in t)
print(t + (4, 5))
print(t * 2)len, in, +, and * all work
Why: These read or build new tuples; none of them change t.
Read the output
Why: Verified by execution: + and * return brand-new tuples, leaving t as (1, 2, 3).
| expression | value |
|---|---|
| len(t) | 3 |
| 2 in t | True |
| t + (4, 5) | (1, 2, 3, 4, 5) |
| t * 2 | (1, 2, 3, 1, 2, 3) |
Concept
Being immutable only blocks changing items. You can still ask read-only questions: .count(x) and .index(x).
count tells you how many times a value appears; index gives the position of its first appearance.
Analogy
Discussion prompt
Explain Tuples still have count and index by analogy to something with no Python Fundamentals in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Being immutable only blocks changing items. You can still ask read-only questions: .count(x) and .index(x).
Worked example
votes = ("yes", "no", "yes", "yes")
print(votes.count("yes"))
print(votes.index("no"))count tallies how many match
Why: Three of the four items are "yes".
index finds the first position
Why: Verified by execution: "no" first appears at index 1. Both read the tuple without changing it.
| call | value |
|---|---|
| votes.count("yes") | 3 |
| votes.index("no") | 1 |
Blank canvas
Draw it
Draw what count and index on a tuple just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Pattern
Predict first
The table runs: (1, 2) == (1, 2) | True · (1, 2) < (1, 3) | True
In Tuples compare item by item, given the rows so far: what is the next one — the row where comparison is (1, 5) < (2, 0)?
Correct: (1, 5) < (2, 0) | True
| comparison | value |
|---|---|
| (1, 2) == (1, 2) | True |
| (1, 2) < (1, 3) | True |
| (1, 5) < (2, 0) | True |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. Two tuples are equal only when all their items match in order.
Worked example
print((1, 2) == (1, 2))
print((1, 2) < (1, 3))
print((1, 5) < (2, 0))Equality checks every item
Why: Two tuples are equal only when all their items match in order.
Less-than compares left to right
Why: Python compares the first items; only if they tie does it look at the next. Verified by execution.
| comparison | value |
|---|---|
| (1, 2) == (1, 2) | True |
| (1, 2) < (1, 3) | True |
| (1, 5) < (2, 0) | True |
Error analysis
Annotate
Walk the callouts on Tuples compare item by item. Each one is a place this is easy to get subtly wrong.
Concept
Same indexing and looping; the split is whether the contents can change.
| feature | tuple | list |
|---|---|---|
| brackets | ( ) | [ ] |
| change items? | no (immutable) | yes (mutable) |
| can be a dict key? | yes | no |
| good for | fixed records | growing collections |
Counterexample
Discussion prompt
Same indexing and looping; the split is whether the contents can change.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Section
Part 9
Pattern
1. Build with commas: t = (a, b) - and (x,) for one item
Why: The comma makes the tuple; a lone (x) is not one.
2. Unpack with matching names: a, b = t
Why: Left count must equal right count, or use *rest for the leftovers.
3. Return several values as one tuple: return x, y
Why: The caller unpacks it into names, or keeps the whole tuple.
4. Do not edit - rebuild if you must change it
Why: Tuples are immutable; slice and join a new one, or use a list.
Pattern
Fixed record, set size? Use a tuple
Why: Coordinates, name/date pairs, multiple return values - things that should not change.
Will grow or shrink? Use a list
Why: You need append, remove, or reorder.
Need it as a dict key? It must be a tuple
Why: Lists cannot be keys because they can change.
Real world
Discussion prompt
Outside this lesson: where does Session 15 - Tuples & Unpacking actually turn up? Name one concrete situation — a job, a piece of software someone ships, a decision somebody has to make — and say which part of Tuple or list? is doing the work in it.
Hint: Vague is the failure mode here. "Engineering" is not a situation; "deciding whether this build is fast enough to ship" is.
Answer:
Session 15 of the Python Fundamentals series, in depth. Tuples as immutable, ordered sequences: creating them with (a, b), the single-element trailing-comma rule, packing and unpacking, multiple assignment, one-line swapping, unpacking tuples in for loops, returning several values from a function, and star (*rest) unpacking.
Check
Watch the comma.
x = (7)
print(type(x))| comma? | type |
|---|---|
| no | ? |
Check your understanding
What does this print?
Answer: A
Why: Without a trailing comma, (7) is just the number 7 in brackets, so its type is int. A tuple would need (7,). Verified by execution.
Check
Trace both assignments.
a, b = 2, 9
a, b = b, a
print(a, b)| step | a | b |
|---|---|---|
| start | 2 | 9 |
| after swap | ? | ? |
Check your understanding
What does this print?
Answer: A
Why: The right side packs the current values (9, 2) first, then unpacks them into a, b - so a becomes 9 and b becomes 2. Verified by execution.
Check
What prints on the second pass?
pairs = [(1, "one"), (2, "two")]
for n, word in pairs:
print(n, word)| pass | n | word |
|---|---|---|
| 1 | 1 | one |
| 2 | ? | ? |
Check your understanding
What is the SECOND line of output?
Answer: A
Why: On the second pass the loop hands over (2, 'two'), and n, word unpacks it to n=2 and word='two', printing 2 two. Verified by execution.
Trade off
Comparison matrix
From Check: unpack in a loop: every row here is a choice with a cost. Fill the word column, then say which row you would actually pick and what you give up for it.
| pass | n | word |
|---|---|---|
| 1 | 1 | one |
| 2 | ? | ? |
Check
Where does the star's value land?
first, *rest = [1, 2, 3, 4]
print(rest)| name | value |
|---|---|
| first | 1 |
| rest | ? |
Check your understanding
What does print(rest) show?
Answer: A
Why: first takes 1, and *rest collects the remaining items into a list, so rest is [2, 3, 4]. A starred target always produces a list. Verified by execution.
Comparison
Comparison matrix
From Check: star unpacking: refill the value column from what you know. The rest of the table is as it appeared.
| name | value |
|---|---|
| first | 1 |
| rest | ? |
Check
Can you assign into it?
t = (1, 2, 3)
t[0] = 99| action | result |
|---|---|
| t[0] = 99 | ? |
Check your understanding
What happens?
Answer: A
Why: Tuples are immutable, so assigning to t[0] raises TypeError: 'tuple' object does not support item assignment. The tuple is never changed. Verified by execution.
Check
What comes back, and in what form?
def min_max(nums):
return min(nums), max(nums)
print(min_max([4, 9, 1, 7]))| min | max | returns |
|---|---|---|
| 1 | 9 | ? |
Check your understanding
What does this print?
Answer: A
Why: return min(nums), max(nums) packs the two results into one tuple, so printing the call shows (1, 9). Verified by execution.
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — What a Tuple Is · Packing & Unpacking · The One-Line Swap · Unpacking in Loops · Returning Many Values · Star Unpacking. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
A tuple is an ordered, immutable group. The comma builds it, unpacking spreads it across names, and immutability makes it a safe fixed record and a valid dict key.
| You write | It means |
|---|---|
| (3, 4) | a two-item tuple |
| (5,) | a one-item tuple (comma required) |
| a, b = b, a | swap in one line |
| for name, score in pairs: | unpack each tuple in the loop |
| return x, y | hand back several values as one tuple |
| first, *rest = seq | first item, then the rest as a list |
Use tuples for fixed records, lists for things that change. Next session we go deeper into sets - unordered collections with no duplicates.
Want this taught 1-on-1? Alexander tutors Python Fundamentals — $55/session, free consultation.