Extended unpacking using the asterisk (*) operator, often called “starred expressions,” fundamentally enhances the capabilities of sequence unpacking in Python. While standard unpacking requires the number of variables on the left to exactly match the number of items in the sequence on the right, the starred expression acts as a variable-length catch-all, absorbing any number of items—including zero—into a list. This pattern, first, *rest = seq, is one of the most common and readable ways to separate the head of a sequence from its tail.

How Extended Unpacking Works

The syntax first, *rest = some_sequence instructs the Python interpreter to perform the following steps:

  1. It checks that some_sequence is iterable.
  2. It assigns the first element yielded by the iterator of some_sequence to the variable first.
  3. It then collects all remaining elements from the iterator into a new list object.
  4. This new list is assigned to the variable prefixed with the asterisk, in this case, rest.

This process is powerful because it works with any iterable object—not just lists or tuples. The star operator can absorb items from the middle or end of any iterable.

# Unpacking from a list
full_name_list = ["Jane", "Marie", "Smith", "PhD"]
title, *first_and_middle, last, degree = full_name_list
print(f"Title: {title}")                 # Output: Title: Jane
print(f"First & Middle: {first_and_middle}") # Output: First & Middle: ['Marie', 'Smith']
print(f"Last: {last}")                   # Output: Last: PhD
print(f"Degree: {degree}")               # Output: Degree: (This would cause an error; see pitfalls)

# Unpacking from a string (which is an iterable of characters)
message = "HelloWorld"
first_char, *other_chars = message
print(first_char)   # Output: H
print(other_chars)  # Output: ['e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']

# Unpacking from a generator expression
squares_gen = (x**2 for x in range(5))
first_square, *other_squares = squares_gen
print(first_square)    # Output: 0
print(other_squares)   # Output: [1, 4, 9, 16]

The Starred Target is Always a List

A critical point to understand is that the variable designated by the * will always receive a list, regardless of the original source type. Even if you unpack from a tuple, string, or generator—which are not lists—the captured elements are placed into a new list object. This behavior ensures consistency; you can always rely on the methods and behavior of a list for the captured items.

# Original is a tuple
point3d = (10, 20, 30)
x, *y_coords = point3d
print(y_coords, type(y_coords))  # Output: [20, 30] <class 'list'>

# Original is a set (order is not guaranteed, but result is still a list)
unique_nums = {5, 2, 8, 1}
first, *others = unique_nums
print(others, type(others))      # Output: e.g., [8, 2, 1] <class 'list'>

Edge Cases and Common Pitfalls

Understanding the behavior in edge cases is vital to avoiding runtime errors.

Single Element Sequences: If the sequence has only one element, the starred variable will become an empty list. There is no ValueError for a mismatch because the * can legally absorb zero items.

short_list = ["only_one"]
head, *tail = short_list
print(head)  # Output: only_one
print(tail)  # Output: [] (an empty list)

Empty Sequences: Unpacking an empty sequence with this pattern will fail with a ValueError because the interpreter cannot assign a value to the first positional variable (first). The starred expression can handle having no items, but the mandatory single variable cannot.

empty_list = []
try:
    first, *rest = empty_list
except ValueError as e:
    print(e)  # Output: not enough values to unpack (expected at least 1, got 0)

Multiple Starred Expressions: A single assignment can only contain one starred expression. The interpreter has no way to determine how to distribute the remaining items between multiple greedy targets.

long_list = [1, 2, 3, 4, 5]
try:
    a, *b, *c = long_list
except SyntaxError as e:
    print(e)  # Output: multiple starred expressions in assignment

Best Practices and Idiomatic Usage

  1. Clarity over Cleverness: Use first, *rest when it genuinely improves readability, such as when processing the head of a list differently from the rest. Avoid overly complex unpacking patterns that might confuse readers.
  2. Ignoring Unneeded Values: The star operator is often paired with the “throwaway” variable _ (a convention for unused values) to ignore parts of a sequence cleanly.
    record = ("ACME", 50, 123.45, (12, 18, 2023))
    name, *_, date = record
    # Now we have `name` and `date`, and we've clearly ignored the middle values.
    print(name)  # Output: ACME
    print(date)  # Output: (12, 18, 2023)
    
  3. Combining with Iterable Unpacking: Starred expressions are invaluable for flattening sequences or combining them.
    list_one = [1, 2, 3]
    list_two = [4, 5, 6]
    combined = [*list_one, *list_two]  # More readable than list_one + list_two for some cases
    print(combined)  # Output: [1, 2, 3, 4, 5, 6]