C.W.K.
Stream
Lesson 03 of 07 · published

Tuples — Immutability, Unpacking, namedtuple

~20 min · tuple, immutable, unpacking, namedtuple

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

Tuples are not 'lists you can't change'

The first beginner take on tuples — "they're like lists but immutable" — misses the point. The reason tuples exist is to express something different: a fixed-shape record. A list is "these are all the same kind of thing in some order" (a sequence of orders, a list of users). A tuple is "these are the parts of one thing" (an x/y coordinate, a row from a database). The shapes are different, and Python uses both shapes idiomatically.

Creating tuples — the comma is the operator

The parentheses you usually see around tuples are optional. The thing that makes a tuple is the comma. x = 1, 2, 3 is a tuple. x = (1, 2, 3) is the same tuple — the parens just disambiguate. The exception: an empty tuple does need parens (()), and a single-element tuple needs a trailing comma ((1,)) — without the comma it's just a parenthesized expression.

Unpacking — Python's quiet superpower

Tuple unpacking is one of those features that, once you internalize it, you start to use everywhere. x, y = 1, 2 swaps two variables in one line. Functions can return multiple values just by returning a tuple. Iterators of tuples can be unpacked in a for loop. The * in first, *rest = my_list grabs "the rest of the elements" into a list.

Immutability — what it really means

You can't reassign elements of a tuple. t[0] = 99 raises TypeError. But if a tuple contains a mutable object, that object is still mutable. t = ([1, 2], 3) — you can't replace t[0], but you can do t[0].append(99). The tuple's structure is frozen; what's inside the structure may not be.

Principle: Tuples can be hashed (and used as dict keys / set members) only if every element is itself hashable. So (1, 2, 3) works as a key. ([1, 2], 3) does not — the inner list breaks the chain.

namedtuple — when a tuple needs names

Indexing into a tuple by position works, but it doesn't read well: row[3] doesn't tell you what 3 means. collections.namedtuple gives a tuple subclass where each field has a name. You still get tuple-ness (immutable, hashable, indexable, unpackable), and you also get row.email. Modern Python has even better options for this — typing.NamedTuple with type hints, and dataclass(frozen=True) — and we'll see those in the OOP track. namedtuple is still the cheapest, fastest way to give a tuple field names.

Code

Creating tuples — and the single-element trap·python
# All of these are tuples — the comma is the operator
empty = ()
single = (1,)              # trailing comma REQUIRED
pair = 1, 2                # parens optional
triple = (1, 2, 3)
mixed = "a", 1, [2, 3]     # tuples can hold anything

# WITHOUT the trailing comma, this is just a parenthesized expression
not_a_tuple = (1)
print(type(not_a_tuple))   # <class 'int'>
print(type(single))        # <class 'tuple'>

# tuple() constructor turns any iterable into a tuple
t = tuple("abc")
print(t)                   # ('a', 'b', 'c')
Unpacking — the everyday superpower·python
# Multiple-assignment
x, y, z = 1, 2, 3

# Swap
a, b = 10, 20
a, b = b, a
print(a, b)                # 20 10

# Functions returning multiple values are returning a tuple
def divmod_(a, b):
    return a // b, a % b

q, r = divmod_(17, 5)
print(q, r)                # 3 2

# Star-unpacking — grab the rest
first, *middle, last = [1, 2, 3, 4, 5]
print(first, middle, last) # 1 [2, 3, 4] 5
Tuples as keys — but only if every element is hashable·python
# A tuple of hashable things is itself hashable
grid = {}
grid[(0, 0)] = "start"
grid[(3, 4)] = "goal"
print(grid[(3, 4)])         # 'goal'

# A tuple containing a list is NOT hashable
bad_key = (1, [2, 3])
try:
    grid[bad_key] = "oops"
except TypeError as e:
    print("failed:", e)     # unhashable type: 'list'
Immutability does NOT mean deeply frozen·python
t = ([1, 2], 3)

# Cannot reassign t[0]
try:
    t[0] = [99]
except TypeError as e:
    print(e)                # 'tuple' object does not support item assignment

# But you CAN mutate what t[0] points at
t[0].append(99)
print(t)                    # ([1, 2, 99], 3)

# That's why this tuple is also unhashable — the inner list ruins it
try:
    hash(t)
except TypeError as e:
    print(e)                # unhashable type: 'list'
namedtuple — names without losing tuple-ness·python
from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)

print(p.x, p.y)             # 3 4    — names
print(p[0], p[1])           # 3 4    — still indexable

x, y = p                    # still unpackable
print(x, y)                 # 3 4

# Immutable
try:
    p.x = 99
except AttributeError as e:
    print(e)                # can't set attribute

# _replace — returns a NEW namedtuple
p2 = p._replace(x=10)
print(p2)                   # Point(x=10, y=4)

External links

Exercise

Define a namedtuple called Stock with fields ticker, price, shares. Create three stocks. (a) Print the value of each holding (price × shares). (b) Sort the three by total value descending. (c) Use _replace to create a new tuple where one stock's price has dropped 10%, leaving the original tuple unchanged. Verify by printing the original after.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.