Python by Example

Tuples

Python tuple basics: packing and unpacking, the one-tuple comma, *rest unpacking, and typing.NamedTuple for named fields.

A Python tuple is an immutable, ordered sequence of values. You create one with parentheses ((1, 2, 3)) or just by separating values with commas (1, 2, 3 - the parentheses are optional). Because tuples are immutable they can be used as dictionary keys and set members, which plain lists cannot.

Packing is creating a tuple; unpacking is pulling its values into separate names. You can pack without parentheses: t = 1, 2 creates a two-tuple. To unpack, put the same number of names on the left: a, b = t. The one-tuple trap: (1) is just the integer 1 wrapped in parentheses. You need a trailing comma to make a real one-element tuple: (1,).

# Packing - parentheses are optional
t = 1, 2
t        # (1, 2)
 
# Unpacking
a, b = t
a  # 1
b  # 2
 
# Swap without a temp variable
a, b = b, a
 
# One-tuple trap
(1)     # just int 1
(1,)    # tuple with one element
type((1,))   # <class 'tuple'>

The *rest syntax in unpacking lets you collect "the middle" or "the tail" into a list. first, *middle, last = seq splits the sequence at both ends, regardless of how long the middle is. The starred name always collects into a plain list even though it came from a tuple.

seq = (1, 2, 3, 4, 5)
 
first, *rest = seq
first   # 1
rest    # [2, 3, 4, 5]  -- list, not tuple
 
first, *middle, last = seq
first   # 1
middle  # [2, 3, 4]
last    # 5
 
# Useful for splitting at known positions
host, *path_parts = "api.example.com/users/42".split("/")
host        # "api.example.com"
path_parts  # ["users", "42"]

typing.NamedTuple lets you give names to tuple positions and add type annotations. You access fields by name (point.x) instead of index (point[0]), which makes code easier to read. The class still behaves as a tuple - you can unpack it, use it as a dict key, and compare it with ==. For legacy code you may see collections.namedtuple, which has no type annotation support.

from typing import NamedTuple
 
class Point(NamedTuple):
    x: float
    y: float
 
p = Point(3.0, 4.0)
p.x       # 3.0
p.y       # 4.0
p[0]      # 3.0  -- still a tuple underneath
 
# Works as a dict key
distances = {Point(0.0, 0.0): "origin", Point(1.0, 0.0): "unit"}
 
# Unpacks like a plain tuple
x, y = p

In production

Tuple immutability is shallow - t = ([], []) lets you mutate t[0].append(1) even though t itself cannot be rebound; if you need deep immutability, use frozenset for sets, freeze contents explicitly, or build with @dataclass(frozen=True). Prefer typing.NamedTuple or @dataclass(frozen=True, slots=True) over collections.namedtuple in new code - you get type checking, stricter equality semantics, and IDE autocomplete on field names.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free