← Home

094. Variable Unpacking

Destructure sequences and iterables into named variables

094. Variable Unpacking

Rohan’s snapshot dict has a "top_processes" list — each entry is a tuple of (name, pid, rss_mb). Extracting fields with index access is noisy.

Before:

for proc in snapshot["top_processes"]:
    name = proc[0]
    pid  = proc[1]
    rss  = proc[2]
    print(f"{name} (PID {pid}): {rss} MB")

After unpacking:

for name, pid, rss in snapshot["top_processes"]:
    print(f"{name} (PID {pid}): {rss} MB")

He also uses star unpacking to split the list into the top process and the rest:

top, *rest = snapshot["top_processes"]
name, pid, rss = top
print(f"Biggest: {name} using {rss} MB")
print(f"{len(rest)} other processes")

And when get_stats() returns (min_mb, max_mb, avg_mb), he unpacks immediately:

# Before:
stats = get_stats(snapshots)
min_mb = stats[0]
max_mb = stats[1]

# After:
min_mb, max_mb, avg_mb = get_stats(snapshots)

The rule: If you find yourself writing x[0], x[1], x[2] for a known-length sequence, unpacking is cleaner, more readable, and will raise a ValueError immediately if the sequence has the wrong length — a free consistency check.


💡 Fun fact: Python’s a, b = b, a swap — with no temporary variable — works because Python evaluates the entire right-hand side as a tuple before any assignment happens. This was one of Python’s earliest elegant features, borrowed from ABC (Guido’s earlier language). In C, the classic interview question is “swap without a temp variable” using XOR — Python makes it a one-liner with no tricks.

⚠️ Watch out: The most common unpacking mistake is a length mismatch — a, b = [1, 2, 3] raises ValueError: too many values to unpack. This is actually a feature (it catches unexpected data shapes), but beginners are surprised when their “unpack three values” code breaks after an API adds a fourth field. Star unpacking (a, b, *rest = items) is the safe alternative when the length might vary.

🤔 Think about it: first, *_, last = items discards the middle values into _. The convention of using _ for “I don’t care about this” is widely adopted in Python. Is this just convention, or does Python actually treat _ specially? What happens if you write first, *_, last = items and then accidentally use _ later in the function?

Learning objectives

  • Unpack tuples and lists into named variables
  • Use star unpacking to capture variable-length sequences
  • Swap values without a temporary variable
  • Use _ to discard unwanted values during unpacking

Key concepts

  • unpacking
  • star unpacking
  • tuple assignment
  • destructuring

Try it

Concept detail

Tuple/sequence unpacking: a, b = (1, 2) assigns a=1, b=2 in one statement.

Swap without a temp variable: a, b = b, a Python evaluates the right side first, then assigns left-to-right.

Star unpacking — captures variable-length middle: first, *rest = [1, 2, 3, 4] → first=1, rest=[2, 3, 4] *start, last = [1, 2, 3, 4] → start=[1, 2, 3], last=4 first, *, last = [1, 2, 3, 4] → first=1, last=4, middle ignored a, *, b = items — _ is convention for “I don’t need this”

Works on any iterable: a, b, c = “xyz” → a=‘x’, b=‘y’, c=‘z’ for k, v in d.items(): → unpack (key, value) pairs in a for loop

Multiple return values use unpacking: def get_bounds(): return 10, 90 # returns a tuple lo, hi = get_bounds() # unpacks the tuple immediately

Nested unpacking: (a, b), c = (1, 2), 3 → a=1, b=2, c=3 Useful when iterating over lists of tuples.

Mismatch raises ValueError: a, b = [1, 2, 3] # ValueError: too many values to unpack This is a feature — it catches unexpected data shapes immediately.

Solution

def swap(a, b):
    return b, a

def first_last(items):
    first, *_, last = items
    return first, last

def split_head_tail(items):
    head, *tail = items
    return head, tail

Tests

def test_swap():
    assert swap(1, 2) == (2, 1)
    assert swap("hello", "world") == ("world", "hello")

def test_first_last_two():
    assert first_last([10, 20]) == (10, 20)

def test_first_last_many():
    first, last = first_last([1, 2, 3, 4, 5])
    assert first == 1
    assert last == 5

def test_split_head_tail():
    head, tail = split_head_tail([1, 2, 3, 4])
    assert head == 1
    assert tail == [2, 3, 4]

def test_split_two_items():
    head, tail = split_head_tail([10, 20])
    assert head == 10
    assert tail == [20]

Resources