12a Tuples Are Immutable, and Tuple Assignment

This lesson introduces the tuple as an immutable sequence, covers its syntax and the singleton comma, shows which list operations carry over and which do not, explains how sequences are compared, and uses tuple assignment to swap variables and return several values at once.

Subject: Python · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Lesson 12a Tuples Are Immutable, and Tuple Assignment

Title

Python · Chapter 12 — Tuples

§12.1-12.3, pp. 115-117

2. By the end of this lesson you can

Objectives

Five things, each one you can check yourself at an interpreter prompt.

Think Python, 2nd edition — Allen B. Downey §12.1-12.3, pp. 115-117 — the pages these objectives are drawn from

3. Before we start: the type chapter 11 promised

Warm-up

The last chapter ended with a restriction and a hint.

Discussion prompt

Chapter 11 said a list cannot be a dictionary key, and that the simplest way round the limitation is a type we had not met. What must that type be like, given the reason lists were rejected?

Hint: Why were lists rejected?

Answer:

Lists were rejected because they are mutable: a key that changes after it is filed can no longer be found where it was put.

So the new type must be a sequence — otherwise it would not be a substitute — and it must be immutable, so its hash value cannot change.

That is exactly what a tuple is. You already know why it has to exist before you know what it looks like, which is a rare and pleasant position to be in.

4. The one idea behind this lesson: a sequence that cannot change

Concept

A tuple is a sequence of values. The values can be any type, and they are indexed by integers, so in that respect tuples are a lot like lists. The important difference is that tuples are immutable.

tuple — An immutable sequence of elements.

There is no consensus on how to pronounce it. Some people say tuh-ple, which rhymes with supple; but in the context of programming, most people say too-ple, which rhymes with quadruple.

Figure (svg): Three columns comparing strings, lists and tuples by what they hold and whether they can change

One row differs. Everything that follows in this chapter follows from it.

Think Python, 2nd edition — Allen B. Downey §12.1-12.3, pp. 115-115

5. The comma makes the tuple

Section

Section 1

6. Not the parentheses — the comma

Concept

Syntactically, a tuple is a comma-separated list of values. Although it is not necessary, it is common to enclose tuples in parentheses.

>>> t = 'a', 'b', 'c', 'd', 'e'
>>> t = ('a', 'b', 'c', 'd', 'e')      # the same thing
>>> t1 = 'a',
>>> type(t1)
<class 'tuple'>
>>> t2 = ('a')
>>> type(t2)
<class 'str'>
What you writeWhat you getNote
'a', 'b', 'c'commas: a tupleparentheses optional
'a',one value and a commaa tuple with one element
('a')parentheses, no commajust a string

To create a tuple with a single element, you have to include a final comma. A value in parentheses is not a tuple — the parentheses are grouping, exactly as they are in arithmetic, and only the comma builds a sequence.

Think Python, 2nd edition — Allen B. Downey §12.1-12.3, pp. 115-115

7. Picture it: what each piece of syntax does

Picture it

Four expressions that look similar and produce three different types.

Figure (svg): A comparison of four similar expressions and the type each produces

Only the last has no comma, and it is the only one that is not a tuple.

Read the parentheses as grouping and the comma as construction, and every one of these becomes predictable.

8. Worked example: the singleton comma

Worked example

The commonest tuple mistake, and it is silent.

>>> t1 = 'a',
>>> type(t1)
<class 'tuple'>
>>> len(t1)
1
>>> t2 = ('a')
>>> type(t2)
<class 'str'>
>>> len(t2)
1
ValueWhat it isNote
t1a tuple holding one stringlen 1
t2a string of one characterlen 1
lenthe same for bothwhich is why the bug hides

Write the singleton with its comma.

Why: To create a tuple with a single element, you have to include a final comma — 'a', is a tuple and ('a',) is the same tuple with grouping added.

Write it without.

Why: A value in parentheses is not a tuple. ('a') is the string 'a', because the parentheses only grouped it.

Notice why this is dangerous.

Why: Both have length 1, both index to 'a', and both print recognisably. The difference shows up only when something depends on the type.

Figure (svg): The state of the program after each line of Worked example the singleton comma, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

The comma makes a one-element tuple; the parentheses alone make nothing at all. len cannot tell them apart, so type is the check.

Verify: Find an operation that distinguishes them.

Why: t1 + t1 gives a two-element tuple; t2 + t2 gives the string 'aa'. Concatenation shows the difference because it produces something of the same type, which is exactly why a singleton written without its comma tends to surface far from where it was written.

9. Predict: what type is this?

Prediction

Parentheses, and no comma.

t = ('hello')
print(type(t))
PartWhat it doesResult
the parenthesesgroup the expressionas in arithmetic
no commanothing constructs a tupleso it stays a string
('hello',)with a commawould be a tuple

Predict first

What does this print?

  • <class 'str'>
  • <class 'tuple'>
  • <class 'list'>
  • An error, because a tuple needs at least two elements

Correct: <class 'str'> — a value in parentheses is not a tuple, because the parentheses only group it.

Why: The comma is what builds a tuple, and there is none here. Adding one — ('hello',) or just 'hello', — gives a one-element tuple. Nothing forbids a one-element or even an empty tuple: tuple() produces the latter, which prints as ().

10. Worked example: the tuple function

Worked example

The counterpart of dict() and list(), with the same warning attached.

>>> t = tuple()
>>> t
()
>>> t = tuple('lupins')
>>> t
('l', 'u', 'p', 'i', 'n', 's')
CallWhat it producesNote
tuple()no argumentan empty tuple, printed ()
tuple('lupins')a sequence argumentone element per character
the namea built-in functionavoid it as a variable name

Call it with nothing.

Why: With no argument, it creates an empty tuple, which prints as a bare pair of parentheses.

Call it with a sequence.

Why: If the argument is a sequence — string, list or tuple — the result is a tuple with the elements of that sequence.

Heed the warning.

Why: Because tuple is the name of a built-in function, you should avoid using it as a variable name — the same caution the book gave for dict and list.

Figure (svg): A pipeline showing a list being converted to a tuple and used as a dictionary key

The step chapter 11 pointed at without naming.

An empty tuple, and a six-element tuple of characters. The conversion works from any sequence, which makes tuple() the way to freeze a list.

Verify: Convert a list and check it is really a tuple.

Why: tuple([1, 2, 3]) gives (1, 2, 3), and type confirms it. That conversion is the practical route to a dictionary key: build the sequence as a list, then freeze it with tuple() when you need to use it as a key.

11. Trap: writing a singleton without its comma

Trap

The trap

A function should return a tuple of one item, so it returns ('result').

Add parentheses to make it a tuple

Why: Which is how every other tuple in the program is written.

The parentheses group and do not construct, so the function returns a string. The caller loops over it and gets one character per pass instead of one item, which produces plausible nonsense rather than an error.

The fix

The comma is the tuple.

Write ('result',) with the trailing comma

Why: Or 'result', without parentheses — both are the same one-element tuple.

Check with type when a singleton misbehaves

Why: len is the same for both, so it cannot tell you anything.

The mnemonic is that the parentheses are optional everywhere and the comma never is. If you can delete a piece of punctuation and still have a tuple, it was not the piece doing the work.

12. Sort: tuple or not?

Sorting

Look for the comma.

Sort into buckets

For each expression, is the result a tuple?

a tuple
1, 2, 3; (1,); tuple('ab'); 'a',
not a tuple
(1); ('a')
yes
Each either contains a comma or was built by the tuple function. The parentheses in two of them are optional grouping that changes nothing.
no
Neither has a comma, so the parentheses merely group a single value — an integer in one case and a string in the other, exactly as parentheses do in arithmetic.

13. Complete it: a one-element tuple

Faded example

One character is doing all the work.

Fill in the blanks

t = ('a',)
print(type(t)) # <class 'tuple'>

Why: Without the comma the parentheses group a string and the type is str. The trailing comma is what makes it a sequence of one — and since len is 1 either way, type is the only check that distinguishes them.

14. Explain it yourself: why is the comma the constructor?

Explain it to yourself

It seems arbitrary until you consider what else the parentheses do.

Discussion prompt

Why can't the parentheses be what makes a tuple? Think about what (2 + 3) * 4 would mean if they could.

Hint: Parentheses already have a job.

Answer:

Parentheses already mean grouping, everywhere in the language. If (2 + 3) built a one-element tuple, arithmetic would stop working.

So the language needed a piece of punctuation that was not already spoken for, and the comma — which already means and then another in argument lists — was the natural choice.

Which is why the parentheses around a tuple are optional and the comma is not. The parentheses are there for readability, and for the places where a bare comma would be ambiguous, such as inside a function call.

15. What immutability changes

Section

Section 2

16. Most list operations carry over. One does not.

Concept

Most list operators also work on tuples. The bracket operator indexes an element and the slice operator selects a range. But if you try to modify one of the elements, you get an error.

>>> t = ('a', 'b', 'c', 'd', 'e')
>>> t[0]
'a'
>>> t[1:3]
('b', 'c')
>>> t[0] = 'A'
TypeError: object doesn't support item assignment
ExpressionWhat happensResult
t[0]indexing works'a'
t[1:3]slicing works, and gives a tuple('b', 'c')
t[0] = 'A'item assignmentTypeError

Because tuples are immutable, you can't modify the elements. But you can replace one tuple with another — which is exactly the situation strings have been in since chapter 8.

Think Python, 2nd edition — Allen B. Downey §12.1-12.3, pp. 116-116

17. Picture it: which operations survive immutability

Picture it

Everything that reads works. Everything that writes does not.

Figure (svg): Two columns separating tuple operations that work from those that raise

The dividing line is exactly whether the operation would change the object.

That is the same line that separated the two halves of lesson 10a's table — only now one side of it is unavailable rather than merely different.

18. Worked example: replacing rather than modifying

Worked example

You cannot change an element. You can build a tuple that differs.

>>> t = ('a', 'b', 'c', 'd', 'e')
>>> t = ('A',) + t[1:]
>>> t
('A', 'b', 'c', 'd', 'e')
PartWhat it producesNote
('A',)a one-element tuplenote the comma
t[1:]everything after the first('b', 'c', 'd', 'e')
the concatenationa NEW tuplenothing was modified
t = ...the name is repointedthe old tuple is discarded

Build the replacement piece.

Why: ('A',) is a one-element tuple — the comma matters, and without it the concatenation would fail with a TypeError about mixing a string and a tuple.

Take the rest by slicing.

Why: t[1:] gives everything from index 1 onward, as a new tuple.

Join and reassign.

Why: This statement makes a new tuple and then makes t refer to it.

Figure (svg): A state diagram showing the name t moved from the original tuple to a new one

The arrow moved. Neither box changed.

('A', 'b', 'c', 'd', 'e'). No tuple was modified — a new one was built and the name was moved to it.

Verify: Ask what happened to the original.

Why: It still exists, unchanged, until nothing refers to it. If another name had been pointing at it, that name would still see ('a', 'b', ...) — which is the opposite of what happens with a list, and is precisely why immutable objects are safe to share.

19. Predict: does this work?

Prediction

Slicing a tuple.

t = ('a', 'b', 'c', 'd')
print(t[1:3])
PartWhat happensResult
slicingreads, does not modifyallowed
the resulta new tuple('b', 'c')
the originaluntouchedas always with slicing

Predict first

What does this print?

  • ('b', 'c')
  • ['b', 'c']
  • A TypeError, because tuples do not support slicing
  • ('a', 'b', 'c')

Correct: ('b', 'c') — slicing reads rather than modifies, so it works, and it produces a tuple.

Why: The slice operator selects a range of elements and creates a new object, which never requires modifying the original — so immutability is no obstacle. Note that the result is a tuple rather than a list: slicing a sequence gives you the same kind of sequence back, which is also true of strings.

20. Worked example: why this makes tuples good keys

Worked example

The restriction chapter 11 imposed, satisfied.

>>> d = {}
>>> d[(1, 2)] = 'point'
>>> d[(1, 2)]
'point'
>>> d[[1, 2]] = 'oops'
TypeError: unhashable type: 'list'
KeyWhyResult
a tuple keyimmutable, so hashableworks
a list keymutable, so unhashableTypeError
whya key's hash must not changechapter 11's argument

Recall the requirement.

Why: Keys have to be hashable, because a dictionary computes where to file a pair from the key's contents.

Check the tuple against it.

Why: A tuple's contents cannot change, so its hash value is fixed for as long as it exists and the pair can always be found where it was put.

Note what this unlocks.

Why: Any sequence can now be a key, by converting it with tuple() — coordinates, pairs of names, a hand of cards.

Figure (svg): The state of the program after each line of Worked example why this makes tuples good keys, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

The tuple works and the list does not, for exactly the reason chapter 11 gave. Immutability is not a limitation here — it is the qualification.

Verify: Test the argument's premise on a nested case.

Why: A tuple containing a list, such as (1, [2]), is still unhashable — because its contents can change after all. That confirms the rule is about whether anything reachable can change, not about the outer type's name, which is a sharper statement than tuples can be keys.

21. Trap: trying to sort a tuple in place

Trap

The trap

A program has a tuple of numbers and calls t.sort() to order them.

Use the method you would use on a list

Why: sort is how you order a sequence, and a tuple is a sequence.

It raises AttributeError: tuples have no sort method, because sorting in place would modify the object. The habit transfers and the method does not.

The fix

Use sorted, which returns rather than modifies.

sorted(t) gives a new sorted LIST

Why: Not a tuple — sorted always returns a list, whatever it was given.

Wrap it if you need a tuple back

Why: tuple(sorted(t)) sorts and refreezes.

The general rule is that every modifying list method is absent from tuples and every returning built-in still works. sorted, min, max, sum, len and in all apply; append, sort, remove and del do not.

22. Error analysis: four tuple operations

Error analysis

Mark each and say whether it raises.

Annotate

  • Line 1 raises TypeError: object doesn't support item assignment. Tuples are immutable, so no element can be replaced.
  • Line 2 is the correct replacement idiom: build a one-element tuple, concatenate the rest, and repoint the name. Nothing is modified.
  • Line 3 loses the comma, so ('A') is a string and the concatenation raises TypeError — you cannot add a string to a tuple. The singleton trap, in the place it does the most damage.
  • Line 4 raises AttributeError, because append modifies and tuples have no modifying methods at all.
  • So three of the four raise, and the one that works differs from a failing one by a single comma.
  • The pattern to take away: reading operations transfer from lists unchanged, writing operations do not exist, and replacement is done by building a new tuple.

Unlike chapter 10's list mistakes, these all announce themselves — which is one practical advantage of an immutable type.

23. Compare: list, tuple and string

Comparison

Fill the blanks. Two rows are about contents and two about change.

Comparison matrix

QuestionListTupleString
What can it hold?any valuesany valuescharacters only
Mutable?yesnono
Can be a dictionary key?noyesyes
Does t[0] = x work?yesno — TypeErrorno — TypeError

The tuple fills the gap in the table: an immutable sequence that can hold anything. Strings and lists each had one of those properties, and neither had both.

24. Explain it: why would anyone want a type you cannot change?

Explain it

Immutability sounds like a missing feature until you see what it buys.

Discussion prompt

A classmate asks why they would ever choose a tuple over a list, when a list can do more. Give them two reasons.

Hint: One is about dictionaries and one is about safety.

Answer:

First: only immutable things can be dictionary keys, so a tuple is the only way to index a dictionary by a pair or a sequence — coordinates, a name and a date, anything compound.

Second: something that cannot change is safe to share. Chapter 10's aliasing bugs are impossible for tuples, because there is no way for one holder to affect another.

And there is a third, softer reason: a tuple in a signature says this will not be modified, which a list cannot say. It is a promise the type system keeps for you rather than one you have to document.

25. Comparing sequences

Section

Section 3

26. Element by element, until they differ

Concept

The relational operators work with tuples and other sequences. Python starts by comparing the first element from each sequence. If they are equal, it goes on to the next elements, and so on, until it finds elements that differ.

>>> (0, 1, 2) < (0, 3, 4)
True
>>> (0, 1, 2000000) < (0, 3, 4)
True
StepThe comparisonWhat happens
first elements0 and 0: equalgo on
second elements1 and 3: differ1 < 3, so True
third elementsnever examinedeven 2000000

Subsequent elements are not considered, even if they are really big. The comparison stops at the first difference, which is why a huge number later in the tuple has no effect on the answer.

Think Python, 2nd edition — Allen B. Downey §12.1-12.3, pp. 116-116

27. Picture it: the comparison stops at the first difference

Picture it

Two tuples, scanned left to right, until they disagree.

Figure (svg): Two tuples aligned element by element with the deciding position marked

The answer is decided at position 1. Nothing after it can change it.

This is exactly how words are ordered in a dictionary — the printed kind. cat comes before cot because of the second letter, and nothing later matters.

28. Worked example: why the big number does not win

Worked example

Two million loses to a four, and the reason is positional.

>>> (0, 1, 2000000) < (0, 3, 4)
True
>>> (0, 3, 1) < (0, 3, 4)
True
>>> (1, 0, 0) < (0, 9, 9)
False
ComparisonWhere it was decidedWhy
first pairdecided at position 11 < 3
second pairpositions 0 and 1 equaldecided at position 2
third pairdecided at position 01 > 0, so False

Scan from the left.

Why: Python compares the first element from each sequence, and moves on only while they are equal.

Stop at the first difference.

Why: That difference decides the answer, and subsequent elements are not considered.

Notice earlier positions dominate completely.

Why: In the third comparison a 1 at position 0 outweighs everything else, however large the later numbers are.

Figure (svg): The state of the program after each line of Worked example why the big number does not win, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

True, True, False. Position matters more than magnitude: an earlier element decides the comparison outright.

Verify: Check the case where one tuple runs out.

Why: (0, 1) < (0, 1, 2) is True: if every compared element is equal and one sequence ends, the shorter one is smaller. That is the same rule that puts car before cart in a printed dictionary, and it makes the ordering complete rather than leaving pairs undecided.

29. Predict: which is smaller?

Prediction

The second elements differ.

print((5, 1, 9) < (5, 2, 0))
StepThe comparisonEffect
position 05 and 5: equalcontinue
position 11 and 2: differ1 < 2
position 2never examined9 vs 0 is irrelevant

Predict first

What does this print?

  • True
  • False
  • An error
  • It depends on the types

Correct: True — the comparison is decided at position 1, where 1 is less than 2.

Why: Python compares element by element until it finds a difference, and stops there. The 9 in the left tuple and the 0 in the right are never examined, because the answer was already determined. Position beats magnitude, which is the rule the book illustrates with two million losing to a three.

30. Worked example: sorting by more than one thing

Worked example

The comparison rule is what makes tuples useful for sorting.

scores = [(3, 'ann'), (1, 'bob'), (3, 'amy')]
print(sorted(scores))
# [(1, 'bob'), (3, 'amy'), (3, 'ann')]
PositionWhat it orders byNote
the first elementthe scorethe primary ordering
tiescompared on the second elementthe name
the resultby score, then alphabeticallyfrom one sort call

Put the primary key first.

Why: Comparison starts at position 0, so whatever is there dominates the ordering.

Put the tie-breaker next.

Why: Two tuples with equal first elements move on to the second, which sorts amy before ann.

Get both from a single sort.

Why: No special options are needed — the element-by-element rule does the whole job.

Figure (svg): A ladder showing three score-name tuples resolved into sorted order

One sort call, two levels of ordering.

Sorted by score and then alphabetically within each score, from one call to sorted. Ordering by several keys is just ordering tuples.

Verify: Reverse the order inside each tuple and re-sort.

Why: With ('ann', 3) the list sorts by name first and score only within a name — a completely different result from the same data and the same sort call. That confirms position is what determines priority, and it is why the order of elements in a sorting tuple is a decision rather than a detail.

31. Trap: expecting the largest element to decide

Trap

The trap

A student reads (0, 1, 2000000) < (0, 3, 4) as False, on the grounds that the left tuple obviously contains more.

Compare the tuples by their contents overall

Why: Which is how you would compare two piles of things.

Sequence comparison is positional, not aggregate. Python finds the first position where they differ and stops there, so the two million is never looked at.

The fix

Read left to right and stop at the first disagreement.

Compare position 0, then 1, and so on

Why: Exactly as words are alphabetised.

If you want an aggregate comparison, compute one

Why: sum(t1) < sum(t2) asks the question you meant, and it is a different question.

The book makes the point explicitly — subsequent elements are not considered, even if they are really big — because the aggregate reading is the natural one and it is wrong.

32. Rank: put these tuples in order

Ranking

Smallest first, by the element-by-element rule.

Put in order

  1. (1,)
  2. (1, 9)
  3. (1, 100)
  4. (2, 0)

Why: All three tuples beginning with 1 come before the one beginning with 2, whatever follows. Among them, (1,) is shortest and runs out first, which makes it smallest; then 9 before 100 at position 1. The 100 never helps, because position 0 was already decided against it relative to (2, 0).

33. Complete it: sort by score then name

Faded example

The tuple's order decides the sort's priority.

Fill in the blanks

pairs = [(score, name) for ...]
ranked = sorted(pairs) # by score, ties broken by name

Why: sorted compares the tuples element by element, so position 0 — the score — dominates and position 1 breaks ties. No key function or special option is needed: the ordering falls out of how sequences compare. Putting name first instead would sort alphabetically and use the score only within a name.

34. Where element-by-element comparison is already familiar

Real world

You have used this rule for years without naming it.

Discussion prompt

Where outside programming do you compare two things by scanning left to right and stopping at the first difference?

Hint: Any ordered list of words or numbers.

Answer:

Alphabetical order is exactly this rule. cat before cot is decided at the second letter, and nothing after it is consulted.

So are dates written year-month-day, phone directories, version numbers, and library shelf marks — all designed so that the most significant part comes first and comparison can stop early.

That design is deliberate: putting the dominant field first is what makes a simple left-to-right scan produce the ordering you want. Choosing the order of a sorting tuple is the same decision, made in code.

35. Tuple assignment

Section

Section 4

36. Several assignments at once

Concept

It is often useful to swap the values of two variables. With conventional assignments you have to use a temporary variable, which is cumbersome; tuple assignment is more elegant.

>>> temp = a
>>> a = b
>>> b = temp

>>> a, b = b, a          # the same swap
PartWhat it isNote
the left sidea tuple of variablesthe targets
the right sidea tuple of expressionsthe values
the orderall expressions evaluated firstthen all assignments

The left side is a tuple of variables and the right side is a tuple of expressions. Each value is assigned to its respective variable — and, crucially, all the expressions on the right side are evaluated before any of the assignments.

Think Python, 2nd edition — Allen B. Downey §12.1-12.3, pp. 116-117

37. Picture it: why the swap does not lose a value

Picture it

Two phases, and the second cannot disturb the first.

Figure (svg): A flowchart showing both right-hand expressions evaluated before either assignment happens

The evaluate-everything-first rule is what makes the temporary variable unnecessary.

Without that rule, assigning to a would destroy the value b needs — which is precisely the problem the temporary variable was solving.

38. Worked example: the swap, step by step

Worked example

Two variables exchange values with no temporary.

>>> a = 1
>>> b = 2
>>> a, b = b, a
>>> a
2
>>> b
1
StepWhat happensNote
evaluate b2held
evaluate a1held
assign to aa becomes 2the held 1 is untouched
assign to bb becomes 1from the held value

Evaluate the whole right side.

Why: All the expressions on the right side are evaluated before any of the assignments — so both old values are in hand before anything changes.

Assign in order.

Why: Each value is assigned to its respective variable, left to right.

See why no temporary is needed.

Why: The held values are not affected by the assignments, so overwriting a cannot destroy what b is about to receive.

Figure (svg): The state of the program after each line of Worked example the swap, step by step, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

a is 2 and b is 1. The rule about evaluation order is doing the work that temp used to do.

Verify: Try the same swap written as two ordinary assignments.

Why: a = b followed by b = a leaves both variables holding 2, because the first assignment destroyed the value the second needed. Comparing the two makes it clear that the swap's elegance comes from the evaluation rule and not from the syntax.

39. Predict: what does the swap leave behind?

Prediction

All the right-hand expressions are evaluated first.

a = 1
b = 2
a, b = b, a
print(a, b)
StepWhat happensResult
evaluate the rightb is 2, a is 1both held
assign to aa becomes 2the held 1 survives
assign to bb becomes 1from the held value

Predict first

What does this print?

  • 2 1
  • 1 2
  • 2 2
  • 1 1

Correct: 2 1 — the values are exchanged, because both right-hand expressions are evaluated before either assignment happens.

Why: The evaluation rule is what makes this work. Writing it as two statements — a = b then b = a — gives 2 2, because the first assignment destroys the value the second needs. That comparison is the clearest demonstration that the rule, not the syntax, is doing the work.

40. Worked example: unpacking a sequence

Worked example

The right side can be any sequence, not only a tuple.

>>> addr = 'monty@python.org'
>>> uname, domain = addr.split('@')
>>> uname
'monty'
>>> domain
'python.org'

>>> a, b = 1, 2, 3
ValueError: too many values to unpack
PartWhat happensNote
split('@')a list of two elementsthe return value
two names on the leftmatched to two elementsin order
a mismatchtwo names, three valuesValueError

Note what split returns.

Why: The return value from split is a list with two elements — the first is assigned to uname and the second to domain.

Note that the type does not matter.

Why: More generally, the right side can be any kind of sequence: string, list or tuple.

Note the one requirement.

Why: The number of variables on the left and the number of values on the right have to be the same, or you get a ValueError.

Figure (svg): A pipeline showing an address split into a list and unpacked into two names

Split then unpack: the standard shape for taking a compound value apart.

Two names bound to the two halves of the address, in one line. The mismatch case fails loudly rather than binding some and ignoring the rest.

Verify: Try unpacking a string.

Why: a, b, c = 'xyz' binds the three characters, because a string is a sequence too. That generality is worth knowing: unpacking is about the number of elements, not about which sequence type produced them.

41. Trap: unpacking the wrong number of values

Trap

The trap

A program unpacks the result of split into two names, and one input contains two separators.

Assume the input has the expected shape

Why: It does for every example the developer tried.

split returns three elements and the assignment raises ValueError: too many values to unpack. The program worked on all the test data and fails on the first awkward record.

The fix

Make the count certain, or check it.

Limit the split

Why: addr.split('@', 1) returns at most two pieces, whatever the input contains.

Or unpack into a list and check its length

Why: Which lets you report a clear error instead of a ValueError from deep inside.

The failure is at least loud — the counts must match, and Python says so. That is better than the silent alternative of binding the first two and dropping the rest, which is what a language without this check would do.

42. Watch the swap: two phases, four steps

Invariant

Step through and watch when each variable changes.

Step through it

Between which two frames would a temporary variable have been needed, if the values were not held?

  1. Two variables with their original values, before the swap statement runs.
  2. Both right-hand expressions have been evaluated and their values held. Neither variable has changed yet.
  3. a receives 2. The held value 1 is unaffected, because it was taken before this assignment.
  4. b receives 1 from the held value, completing the exchange with no temporary variable.

Between the third and fourth: assigning to a would have destroyed the 1 that b needs. Holding both values first is exactly what temp used to do, and the language does it for you.

43. Complete it: split and unpack

Faded example

One line to take an address apart.

Fill in the blanks

addr = 'monty@python.org'
uname, domain = addr.split('@')
print(domain) # python.org

Why: split returns a list of two elements, and the two names on the left are bound to them in order. The right side can be any sequence — string, list or tuple — but the counts must match, or the assignment raises ValueError: too many values to unpack.

44. Think it through: why must the right side be evaluated first?

Socratic

The rule looks like a detail and it is the whole mechanism.

Discussion prompt

Suppose Python assigned each variable as soon as its value was computed, left to right. What would a, b = b, a produce, and why?

Hint: Work out what b would be assigned from.

Answer:

a would be assigned b's value first, so a becomes 2. Then b would be assigned a's value — but a is now 2, so b becomes 2 as well.

Both variables would end up holding 2 and the original 1 would be lost. That is exactly the bug the temporary variable existed to prevent.

So evaluating everything before assigning anything is not a nicety; it is what makes simultaneous assignment mean something. Once you see that, the swap stops being a trick and becomes an obvious consequence of the rule.

45. Tuples as return values

Section

Section 5

46. One value that behaves like several

Concept

Strictly speaking, a function can only return one value — but if the value is a tuple, the effect is the same as returning multiple values.

def min_max(t):
    return min(t), max(t)

>>> min_max([3, 1, 4, 1, 5])
(1, 5)
>>> low, high = min_max([3, 1, 4, 1, 5])
>>> low
1
PartWhat happensNote
return min(t), max(t)the comma makes a tupleone value returned
received as one namea tuple(1, 5)
received as two namestuple assignment unpacks itlow and high

max and min are built-in functions that find the largest and smallest elements of a sequence. min_max computes both and returns a tuple of two values — and the caller chooses whether to take it as one thing or as two.

Think Python, 2nd edition — Allen B. Downey §12.1-12.3, pp. 117-117

47. Picture it: one value, two ways to receive it

Picture it

The function does the same thing; the call site decides the shape.

Figure (svg): Two columns showing the same returned tuple received as one name or unpacked into two

The same return value. Tuple assignment is what makes the right-hand column possible.

This is why §12.2 comes before §12.3 in the book: unpacking is what makes returning a tuple feel like returning several values.

48. Worked example: divmod, and why it exists

Worked example

Two results from one computation.

>>> t = divmod(7, 3)
>>> t
(2, 1)
>>> quot, rem = divmod(7, 3)
>>> quot
2
>>> rem
1
FormWhat you getNote
divmod(7, 3)quotient and remainderas a tuple
stored as tone name holding both(2, 1)
unpackedtwo names2 and 1

Note the inefficiency it avoids.

Why: If you want to divide two integers and compute the quotient and remainder, it is inefficient to compute x//y and then x%y — it is better to compute them both at the same time.

Note what it returns.

Why: The built-in function divmod takes two arguments and returns a tuple of two values, the quotient and the remainder.

Note the caller's choice.

Why: You can store the result as a tuple, or use tuple assignment to store the elements separately.

Figure (svg): The state of the program after each line of Worked example divmod, and why it exists, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

(2, 1), received either way. The tuple is the mechanism that lets one return statement deliver two results.

Verify: Check the two results against the division.

Why: 7 divided by 3 is 2 with 1 left over, and 2 * 3 + 1 is 7 — a consistency check of exactly the kind lesson 11c described. It also confirms the order: quotient first, remainder second, which is worth fixing in memory since unpacking them the wrong way round is silent.

49. Predict: what does the function return?

Prediction

One return statement, two expressions.

def min_max(t):
    return min(t), max(t)

print(min_max([3, 1, 4, 1, 5]))
PartWhat it computesResult
min(t)the smallest1
max(t)the largest5
the commabuilds a tuple(1, 5)

Predict first

What does this print?

  • (1, 5)
  • 1 5
  • [1, 5]
  • Two separate values, printed on two lines

Correct: (1, 5) — the comma makes a tuple, so the function returns one value that happens to hold two.

Why: Strictly speaking a function can only return one value, and the tuple is how it delivers two. Printing it shows the tuple's own form, with parentheses and a comma. Writing low, high = min_max(...) instead would unpack it into two names, which is the usual way to receive it.

50. Worked example: writing your own

Worked example

The comma in the return statement is the whole trick.

def min_max(t):
    return min(t), max(t)

def first_last(t):
    return t[0], t[-1]

>>> low, high = min_max([3, 1, 4])
>>> a, z = first_last('python')
PartWhat happensNote
the commabuilds a tupleone value returned
no parentheses neededthe comma is enoughthough they are common
the callerunpacks with tuple assignmenttwo names

Separate the results with a comma.

Why: return min(t), max(t) returns one value — the tuple (min, max) — because the comma builds it.

Leave the parentheses off or put them on.

Why: return (min(t), max(t)) is identical. The comma is doing the work either way.

Let the caller decide the shape.

Why: They can take one name or unpack into two, and the function does not need to know which.

Figure (svg): A call diagram showing a function returning a tuple that the caller unpacks into two names

Two computations, one return value, two names at the call site.

Two functions each returning a pair. Nothing special is needed in the return statement beyond a comma.

Verify: Check what happens if the caller unpacks into the wrong number of names.

Why: a, b, c = min_max(t) raises ValueError: not enough values to unpack. So the function's shape is enforced at the call site rather than silently mismatched — the same counting rule as any other tuple assignment.

51. Trap: unpacking a returned pair the wrong way round

Trap

The trap

A caller writes rem, quot = divmod(a, b), reasoning that the remainder is the interesting part.

Name the variables in the order you care about

Why: Which has no bearing on the order the function returns them in.

The names are bound by position, not by meaning, so quot holds the remainder and vice versa. Nothing raises, and every calculation afterwards is wrong.

The fix

Match the order the function returns.

Check the documentation or the return statement

Why: divmod returns the quotient first — the name says so, div before mod.

Sanity-check the values once

Why: For divmod(7, 3), the quotient is 2 and the remainder 1; if your names disagree, they are swapped.

This is a silent failure in a chapter otherwise full of loud ones, and it is worth a moment's care: tuple assignment binds strictly by position and cannot know what you meant.

52. Discriminate: one value or several?

Discrimination

Ask what the call site ends up holding.

Sort into buckets

For each line, does the caller end up with one name or several?

one name holding a sequence
t = divmod(7, 3); pair = min_max(t); result = addr.split('@')
several names, unpacked
quot, rem = divmod(7, 3); low, high = min_max(t); uname, domain = addr.split('@')
one
Each binds a single name to the whole returned sequence, which then has to be indexed to get at its parts. The function returned the same thing in every case.
many
Each uses tuple assignment to bind the elements to separate names, in order. The counts must match, or the assignment raises ValueError.

53. Complete it: return two values

Faded example

The comma is all that is needed.

Fill in the blanks

def min_max(t):
return min(t), max(t)

Why: The comma builds a tuple, so the function returns one value holding both results — and a caller can unpack it into two names with tuple assignment. Parentheses around the pair are optional and change nothing; as everywhere else in this lesson, the comma is what constructs.

54. Explain it: I thought a function could only return one thing

Explain it

It can. The resolution is worth stating precisely.

Discussion prompt

A classmate is confused because min_max seems to return two values when they were told a function returns one. Resolve it for them.

Hint: How many objects came back?

Answer:

It does return one value: a tuple. The comma in the return statement built a single object that happens to contain two things, and one object came back.

What makes it feel like two is what happens at the call site: tuple assignment immediately takes the pair apart into two names, so the caller never handles the tuple as such.

So the rule is intact and the convenience is real. It is worth saying that way round, because it explains why low, high = f() works for anything f returns that has two elements — a tuple, a list, even a two-character string.

55. Compare: list and tuple

Comparison

Fill the blanks. Everything follows from one row.

Comparison matrix

QuestionListTuple
How is it written?square bracketscommas, with optional parentheses
Can an element be replaced?yes — t[0] = xno — TypeError
How do you get a modified version?modify it in placebuild a new one and repoint the name
Can it be a dictionary key?no — unhashableyes — its hash cannot change

The second row is the difference and the rest are consequences. Reading operations transfer unchanged; writing operations do not exist.

56. The procedure: returning several values

Pattern

Five steps, and the last one is where the silent bug lives.

  1. Compute all the results inside the function, together, if computing them separately would repeat work.
  2. Return them separated by commas, which builds a tuple — parentheses optional.
  3. At the call site, decide whether you want them together or apart.
  4. To take them apart, put the same number of names on the left of the assignment.
  5. Match the order the function returns, since the names are bound by position and nothing checks the meaning.

Step 4's count is checked and step 5's order is not. A wrong count raises ValueError; a wrong order produces confident nonsense.

Python documentation — Data Structures Data Structures

57. Check yourself 1 of 3: the singleton

Check

One of these is a tuple.

a = ('x')
b = ('x',)
print(type(a) == type(b))
ExpressionWhat it buildsType
('x')parentheses onlya string
('x',)a commaa tuple of one
comparing typesstr against tupleFalse

Check your understanding

What does this print?

  • A. False (correct)
  • B. True
  • C. An error is raised
  • D. It depends on the value inside

Answer: A

Why: a is a string and b is a one-element tuple, so their types differ. A value in parentheses is not a tuple; to create a tuple with a single element you have to include a final comma. Both have length 1, which is why this mistake usually surfaces somewhere other than where it was made.

Why B tempts people
This would require both to be tuples. Only the one with a comma is.
Why C tempts people
Both lines are legal — that is the problem. The failure is silent.
Why D tempts people
The value is irrelevant; the punctuation decides the type in both cases.

58. Check yourself 2 of 3: modifying a tuple

Check

One of these succeeds.

Check your understanding

Given t = ('a', 'b', 'c'), which line leaves t as ('A', 'b', 'c')?

  • A. t[0] = 'A'
  • B. t = ('A',) + t[1:] (correct)
  • C. t = ('A') + t[1:]
  • D. t.replace(0, 'A')

Answer: B

Why: You cannot modify a tuple's elements, but you can replace one tuple with another: this statement makes a new tuple and then makes t refer to it. The singleton needs its comma, and the slice supplies the rest.

Why A tempts people
This raises TypeError: object doesn't support item assignment, which is what immutable means.
Why C tempts people
Without the comma, ('A') is a string, and adding a string to a tuple raises TypeError.
Why D tempts people
Tuples have no replace method. The string method of that name returns a new string and does not take an index.

59. Check yourself 3 of 3: tuple assignment

Check

Two names, three values.

a, b = 1, 2, 3
PartHow manyEffect
the left sidetwo variablestwo targets
the right sidethree valuesone too many
the resultValueErrorcounts must match

Check your understanding

What happens?

  • A. a gets 1, b gets 2, and 3 is discarded
  • B. A ValueError: too many values to unpack (correct)
  • C. a gets 1 and b gets (2, 3)
  • D. b gets 3 and a gets 1

Answer: B

Why: The number of variables on the left and the number of values on the right have to be the same. Python does not silently discard, group, or reorder — it raises, which means a shape mismatch is found at the assignment rather than several steps later.

Why A tempts people
This is what a language without the check would do, and it is exactly the silent failure the ValueError prevents.
Why C tempts people
Grouping the remainder is possible in later Python with a starred name, but plain tuple assignment requires an exact match.
Why D tempts people
Nothing skips a value. Names are bound strictly in order, and only when the counts agree.

60. Where this shows up outside this course

Real world

A fixed-shape record is a different thing from a list of items.

Discussion prompt

Think of some data with a fixed number of parts in a fixed order — a date, a coordinate, a name and a score. What would go wrong if you treated it as a list that anyone could lengthen?

Hint: What does position mean in each case?

Answer:

In a fixed record each position means something specific: the second element of a coordinate is the y value, not merely the second thing. In a list, position usually means only order.

If it could be lengthened or reordered, every piece of code that reads position 1 would be wrong, and nothing would announce it. The shape is part of the meaning.

That is the everyday case for tuples: a list is a collection of similar things whose length varies, and a tuple is a record of dissimilar things whose shape does not. Choosing between them says which kind of data you have.

61. Confidence wager: commit before you check

Commit first

Answer, then rate your confidence. This one costs people an afternoon.

Predict first

What is the type of ('a')?

  • str
  • tuple
  • list
  • It depends on what is done with it

Correct: str — a value in parentheses is not a tuple, because the parentheses only group it.

Why: The comma is what builds a tuple, not the parentheses. ('a') is the string 'a' with redundant grouping, exactly as (2) is the integer 2. To make a one-element tuple you need the final comma: ('a',) or just 'a',. This is the chapter's most expensive trap because nothing raises and len reports 1 for both — so a function that was supposed to return a one-element tuple returns a string, the caller loops over it, and gets one character per pass instead of one item. The check is type, since len cannot distinguish them.

62. Explain it to someone else

Explain it

Three sequence types, and where the new one fits.

Discussion prompt

A classmate asks what a tuple is for, given that lists exist. Place it in the table of sequence types and give the two things only it can do.

Hint: Cross two properties: what it holds, and whether it can change.

Answer:

Draw the grid: strings hold characters and cannot change; lists hold anything and can change; tuples hold anything and cannot change. The tuple fills the empty square.

The first thing only a tuple can do is be a dictionary key while holding a sequence of arbitrary values — because its hash cannot change, which is exactly chapter 11's requirement.

The second is to be handed around safely. A tuple cannot be modified by anyone who receives it, so none of chapter 10's aliasing surprises are possible — the immutability is a guarantee rather than a restriction.

63. Exit ticket

Exit ticket

One honest answer. It decides what the next lesson opens with.

Predict first

Which of these is still least solid for you?

  • Tuple syntax, and the comma that makes a singleton
  • Which operations work on a tuple and which raise
  • Comparing sequences element by element
  • Tuple assignment: swapping, unpacking, and returning several values

Correct: Whichever you picked is the right answer — this one is for you, not for a mark.

Why: The syntax is where nearly everyone is caught once, and the singleton comma is worth over-learning because its failure is silent. Which operations survive follows entirely from immutability, so it is more a rule to derive than to memorise. The comparison rule matters most when you start sorting by several keys. And tuple assignment is the part you will use every day — the evaluation-order rule behind the swap is the piece that turns it from a trick into something predictable.

64. Synthesis: draw the map of this lesson

Connect it up

One page, from memory.

Draw it

Draw a three-by-two grid with strings, lists and tuples down one side and what it holds and can it change across the top, and fill it in. Beside it, write the four ways to build a tuple and mark which one is not a tuple at all. Underneath, write the swap statement and label the two phases the interpreter goes through, then write a function that returns two values and the two ways a caller can receive them.

65. What you can do now

Recap

Three pages, and the sequence type that completes the set.

If you remember one thingIt is this
From the syntaxThe comma makes the tuple. ('a') is a string.
From immutabilityReading operations transfer from lists; writing operations do not exist.
From comparisonPosition beats magnitude — the first difference decides everything.
From tuple assignmentThe whole right side is evaluated before any assignment happens.
From return valuesA function returns one value; a tuple is how it delivers several.

The next lesson uses the gather and scatter operators to write functions that take any number of arguments, and introduces zip and enumerate — the tools for traversing two sequences at once, or a sequence and its own indices.

Think Python, 2nd edition — Allen B. Downey §12.1-12.3, pp. 115-117 — everything on these slides traces back here

Sources

  1. Think Python, 2nd edition — Allen B. Downey — Allen B. Downey, Think Python: How to Think Like a Computer Scientist, 2nd edition (Green Tea Press, 2015), §12.1-12.3, pp. 115-117
  2. Python documentation — Data Structures
  3. Python documentation — Built-in Types

Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108