Sequence unpacking, often called iterable unpacking or multiple assignment, is a concise and Pythonic mechanism for assigning the elements of an iterable to a series of variables in a single, declarative statement. The syntax a, b = iterable is its most fundamental form. This operation is predicated on a simple rule: the number of variables on the left-hand side of the assignment must exactly match the number of items the iterable on the right-hand side will produce. This is not a suggestion but a requirement enforced by the Python interpreter. When executed, it iterates over the iterable, assigning the first value to the first variable, the second value to the second variable, and so on, until all are assigned.

# Basic unpacking of a tuple
coordinates = (10, 20)
x, y = coordinates
print(f"x: {x}, y: {y}")  # Output: x: 10, y: 20

# It works with any iterable: lists, strings, generators, etc.
first_letter, second_letter = "AB"
print(first_letter)  # Output: A

def number_generator():
    yield 1
    yield 2

num_a, num_b = number_generator()
print(num_a, num_b)  # Output: 1 2

The Unpacking Process is an Iteration

It’s crucial to understand that a, b = iterable is semantically equivalent to using an iterator manually. The statement creates an iterator from the iterable and calls next() on it for each target variable. This means the right-hand side can be any object that is iterable, not just sequences like tuples or lists. This includes sets, dictionaries (which iterate over keys), files, and custom iterable objects. This design is a cornerstone of Python’s duck typing—it doesn’t care what the object is, only that it can be iterated over the requisite number of times.

# Unpacking from a set (order is not guaranteed!)
a, b, c = {'apple', 'banana', 'cherry'}
print(a, b, c)  # Output will be three fruit names in an arbitrary order

# Unpacking from a dictionary (iterates over keys)
dict_iterable = {'x': 1, 'y': 2, 'z': 3}
key1, key2, key3 = dict_iterable
print(key1, key2, key3)  # Output: x y z (order may vary in Python <3.7)

The ValueError: Too Many/Too Few Values

The most common pitfall arises from a mismatch between the expected and actual number of items. If the iterable has more elements than there are variables to assign them to, a ValueError: too many values to unpack is raised. Conversely, if the iterable has fewer elements, a ValueError: not enough values to unpack is raised. This strictness ensures clarity and prevents silent errors where data might be accidentally ignored or variables might be left undefined.

# This will raise ValueError: too many values to unpack (expected 2)
point = (5, 10, 15)
# x, y = point

# This will raise ValueError: not enough values to unpack (expected 3, got 2)
values = [1, 2]
# a, b, c = values

Swapping Variables Idiomatically

One of the most elegant applications of sequence unpacking is swapping the values of two variables without needing a temporary variable. The statement a, b = b, a works because the right-hand side is first evaluated as a tuple (b, a). This tuple is then unpacked, assigning its first element (b’s value) to a and its second element (a’s value) to b. This method is not only concise but also highly efficient and considered the standard, idiomatic way to perform a swap in Python.

a = "Hello"
b = "World"
print(f"Before: a={a}, b={b}")  # Output: Before: a=Hello, b=World

a, b = b, a  # The swap
print(f"After: a={a}, b={b}")   # Output: After: a=World, b=Hello

Unpacking with Mismatched Lengths using the Starred Expression

For cases where the number of items is variable, basic unpacking fails. This is where the starred expression (*) becomes essential. A variable prefixed with an asterisk (*) will absorb zero or more leftover values from the iterable into a list. This allows you to handle iterables of unknown length gracefully. There can only be one starred term in the unpacking assignment.

# Capturing the "rest" of the values
first, *middle, last = [1, 2, 3, 4, 5, 6]
print(first)   # Output: 1
print(middle)  # Output: [2, 3, 4, 5] (a list)
print(last)    # Output: 6

# The starred variable can capture zero items
start, *rest = [99]
print(start)  # Output: 99
print(rest)   # Output: [] (an empty list)

# It can be used in any position
*beginning, final = (10, 20, 30)
print(beginning)  # Output: [10, 20]
print(final)      # Output: 30

Best Practices and Considerations

  1. Clarity over Cleverness: While unpacking is powerful, ensure its use enhances readability. Deeply nested or overly complex unpacking can be difficult to decipher.
  2. Use with Well-Structured Data: Unpacking is ideal for fixed-format data, like a known-length tuple returned from a function or a line split into a predictable number of columns.
  3. Beware of Large Iterables with *: Using *rest on a very large iterable will create a list of all the leftover elements, which could have significant memory implications. If you only need to process items one by one and don’t need a random-access list, using a loop or itertools.islice might be more memory efficient.
  4. Underscore for Unused Variables: A common convention is to use the underscore (_) for variables that must be assigned due to unpacking syntax but whose values are intentionally ignored.
# Ignoring unwanted values using underscore
filename, _, extension = "my_report.txt".partition('.')
print(filename)   # Output: my_report
print(extension)  # Output: txt

# Ignoring multiple values with a starred underscore (Python 3.11+)
# This avoids creating a list of ignored items, saving memory.
record = ("ACME", 100, 123.45, 50.00, "NYSE")
name, shares, *_, exchange = record
print(name)      # Output: ACME
print(exchange)  # Output: NYSE
# _ is a list here, but the name signals intent to ignore.