Unpacking
Unpacking spreads the elements of an iterable (or the entries of a mapping) into separate names, function arguments, or literal collections in one expressive step.
Unpacking is Python's syntax for pulling the individual elements out of an iterable — or the key/value pairs out of a mapping — and distributing them into separate targets in a single expression. It shows up in assignment (a, b = pair), in function calls (f(*args, **kwargs)), in function definitions, and inside list/tuple/set/dict literals. Mastering unpacking makes everyday code shorter and clearer, and understanding its exact rules (how many targets, what * collects, what order keys land in) avoids a whole class of ValueErrors and subtle bugs.
What Is Unpacking
Unpacking takes an iterable (list, tuple, string, generator, or anything implementing the iterator protocol) and assigns its elements to multiple targets at once, instead of indexing into it manually. a, b = (1, 2) binds a = 1 and b = 2 in one statement. The right-hand side just needs to be iterable and, for plain unpacking, produce exactly as many items as there are targets on the left.
The left-hand side is a comma-separated list of target names; Python iterates the right-hand side and binds each item positionally.
point = (3, 4)
x, y = point
print(x, y) # 3 4
# Works with any iterable, not just tuples
first, second, third = "abc"
print(first, second, third) # a b c
name, age = ["Ada", 36]
print(name, age) # Ada 36Basic Rules: Count Must Match
Plain (non-starred) unpacking requires an exact count match: the number of targets on the left must equal the number of items produced by the iterable on the right, or Python raises ValueError. This is a language guarantee, not just a convenience — it exists to catch bugs where you expected a fixed-shape structure and got something else.
Unpacking also works on nested structures: a, (b, c) = 1, (2, 3) binds a=1, b=2, c=3 by mirroring the shape of the right-hand side.
try:
a, b = (1, 2, 3)
except ValueError as e:
print(e) # too many values to unpack (expected 2)
try:
a, b, c = (1, 2)
except ValueError as e:
print(e) # not enough values to unpack (expected 3, got 2)Extended Unpacking with a Starred Target
A single starred name (*rest) on the left-hand side collects any number of leftover items into a list, while the other names take exactly one item each. The starred target can appear first, last, or in the middle — Python fills the fixed (non-starred) targets first and gives everything else to the star. At most one starred target is allowed per unpacking (PEP 3132, Python 3.0+); using two raises a SyntaxError.
The starred name always ends up as a list, even when it captures nothing or exactly one item.
first, *middle, last = [1, 2, 3, 4, 5]
print(first, middle, last) # 1 [2, 3, 4] 5
head, *tail = [10, 20, 30]
print(head, tail) # 10 [20, 30]
*init, final = [10, 20, 30]
print(init, final) # [10, 20] 30
# The starred target can also collect zero or one items
a, *b, c = [1, 2]
print(a, b, c) # 1 [] 2The * Operator: Spreading an Iterable Into a Call or Literal
The single-star * operator, used inside a function call or a literal, spreads an iterable's elements out as individual items — the reverse role from a starred *target* in assignment (which gathers). * works on any iterable in a call; inside a list/tuple/set literal it is called an 'unpacking generalization' (PEP 448, Python 3.5+) and can appear more than once and be mixed with plain elements.
nums = [1, 2, 3]
def add3(a: int, b: int, c: int) -> int:
return a + b + c
print(add3(*nums)) # 3 -- same as add3(1, 2, 3)
# Merging iterables into new literals
a = [1, 2]
b = [3, 4]
combined = [*a, *b, 5]
print(combined) # [1, 2, 3, 4, 5]
merged_set = {*a, *b}
print(merged_set) # {1, 2, 3, 4}
# Multiple stars are allowed and can mix with plain values
first, *rest = [*a, *b]
print(first, rest) # 1 [2, 3, 4]The ** Operator: Spreading a Mapping
The double-star ` operator spreads a mapping's key/value pairs — into keyword arguments of a call, or into a new dict literal (also PEP 448). Every key spread with must be a string when used as keyword arguments to a call, because keyword argument names must be valid identifiers; a plain dict literal has no such restriction. When merging dicts with , later keys silently overwrite earlier ones with the same key — order of the ` expressions in the literal determines the winner.
The rightmost **overrides takes precedence for duplicate keys because it is applied last while building the literal.
Since Python 3.9, dict1 | dict2 (the merge operator) offers an alternative to {dict1, dict2} for merging two dicts with the same last-writer-wins semantics — see the Dict Methods / Merging topic for details.
defaults = {"color": "blue", "size": "M"}
overrides = {"size": "L"}
merged = {**defaults, **overrides}
print(merged) # {'color': 'blue', 'size': 'L'} -- overrides wins on 'size'
def describe(color: str, size: str) -> str:
return f"{size} {color} shirt"
print(describe(**merged)) # L blue shirtFunction Argument Unpacking
At a call site, *args unpacks an iterable positionally and **kwargs unpacks a mapping as keywords; both can be combined, and a call can mix them with ordinary positional and keyword arguments as long as Python's normal argument-ordering rules are respected (positional before keyword).
This is the mirror image of variadic *parameters* in a function definition (def f(*args, **kwargs)), which gather extra positional/keyword arguments into a tuple and dict respectively inside the function body. The parameter side is covered in depth in the Args & Kwargs / Function Definitions topics; this topic focuses on the unpacking (spreading) side used at call sites and in assignment.
def connect(host: str, port: int, *, timeout: float = 5.0, retries: int = 3) -> str:
return f"{host}:{port} timeout={timeout} retries={retries}"
args = ("localhost", 8080)
kwargs = {"timeout": 2.0, "retries": 1}
print(connect(*args, **kwargs))
# localhost:8080 timeout=2.0 retries=1Unpacking in for Loops
Because a for target list follows the same rules as assignment unpacking, iterating over a sequence of fixed-size tuples (like dict.items() or enumerate()) commonly unpacks each element directly in the loop header instead of indexing inside the body.
scores = {"alice": 90, "bob": 85}
for name, score in scores.items():
print(f"{name}: {score}")
for index, value in enumerate(["x", "y", "z"], start=1):
print(index, value)Swapping Values Without a Temporary Variable
A very common idiom uses tuple unpacking to swap two names' bindings in one statement. Python first builds the right-hand-side tuple (b, a) from the *current* values, then unpacks it into the left-hand targets — so no manual temporary variable is needed and there is no window where the swap is only half done.
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1
items = [1, 2, 3, 4]
items[0], items[-1] = items[-1], items[0]
print(items) # [4, 2, 3, 1]Ignoring Values During Unpacking
By convention, a single underscore _ is used as a throwaway name for a value you must unpack positionally but don't need. _ is not special syntax — it's an ordinary (if conventionally 'unused') identifier — so it still gets bound and can even be reused as a real name later, though doing so is confusing and best avoided.
record = ("Ada Lovelace", 1815, "mathematician")
name, _, profession = record
print(name, profession) # Ada Lovelace mathematician
# Combine with a star to discard a run of values
first, *_, last = range(10)
print(first, last) # 0 9Edge Cases
A few edge cases catch newcomers: unpacking a string unpacks its individual characters (a string is iterable character-by-character), not the whole string as one item; unpacking a dict directly (a, b = {'x': 1, 'y': 2}) unpacks its *keys*, not its items, because iterating a dict yields keys; and a single-element tuple target still needs a trailing comma to be treated as a target list rather than a parenthesized name.
# Iterating a dict yields keys, not (key, value) pairs
d = {"x": 1, "y": 2}
a, b = d
print(a, b) # x y -- likely not what was intended; use d.items()
# A generator is consumed exactly once during unpacking
gen = (n * n for n in range(3))
p, q, r = gen
print(p, q, r) # 0 1 4Practical Usage
Unpacking is idiomatic for: returning multiple values from a function (return min_val, max_val then lo, hi = get_range(...)), destructuring rows from CSV/database results, forwarding arguments through wrapper functions (def wrapper(*args, **kwargs): return inner(*args, **kwargs)), building configuration dicts from layered defaults, and writing concise loop headers over paired data.
def min_max(values: list[float]) -> tuple[float, float]:
return min(values), max(values)
lo, hi = min_max([4, 1, 7, 3])
print(lo, hi) # 1 7Best Practices
Prefer unpacking over manual indexing (row[0], row[1]) when a structure's shape is fixed and known — it documents intent and self-checks the shape via ValueError. Use *rest rather than slicing when splitting off known-position elements from the rest of a sequence. Avoid over-long starred unpacking chains that obscure how many real values you expect; if a return value can vary widely in shape, a dict or dataclass communicates intent better than a tuple that must be unpacked positionally.
Key Takeaways
Iterable unpacking assigns items from any iterable to multiple targets in one statement; the count must match exactly unless a starred target is used.
A starred target (*rest) collects zero or more leftover items into a list; only one starred target is allowed per unpacking.
* in an expression spreads an iterable into individual positional items (call arguments or list/tuple/set literal elements).
` spreads a mapping into keyword arguments or into a new dict literal; later ` sources overwrite earlier ones on key collision.
Tuple unpacking enables the temp-variable-free swap idiom a, b = b, a.
_ is an ordinary name used by convention, not special syntax, to mark an unpacked value as intentionally unused.
Unpacking a dict directly yields its keys, not key/value pairs — use .items() when pairs are wanted.
record = ("Ada", 1815, "mathematician", "logician") name, year, profession = record # ValueError: too many values to unpack record = ("Ada", 1815, "mathematician", "logician") name, year, *roles = record print(name, year, roles) # Ada 1815 ['mathematician', 'logician']
When a sequence can have more items than fixed targets, use a starred target to absorb the extras instead of hardcoding an exact count.
config = {"host": "localhost", "port": 8080} host, port = config # unpacks the KEYS: 'host', 'port' -- not the values config = {"host": "localhost", "port": 8080} host, port = config.values() # or: (host, port), = [tuple(config.items())]
Iterating a dict directly yields its keys, not its values or (key, value) pairs; unpack .items() or .values() explicitly.
def f(a, b, *args, kwargs):... opts = {1: "x"} # non-string key f(1, 2, opts) # TypeError: keywords must be strings def f(a, b, *args, kwargs):... opts = {"c": "x"} # string keys only for keyword unpacking f(1, 2, opts)
** unpacking into a call requires every key to be a string, since keyword argument names must be valid identifiers.
total = sum(*[1, 2, 3]) # TypeError: sum expected at most 2 arguments, got 3 total = sum([1, 2, 3]) # pass the iterable directly -- sum() takes one iterable argument
Only unpack an iterable with * when the target function actually accepts that many separate positional arguments; check the signature first.
What does a, *b, c = [1, 2, 3, 4] bind a, b, and c to?
a = 1, b = [2, 3], c = 4. The starred name absorbs everything left over after the fixed targets take one item each from each end.
Why does x, y = [1, 2, 3] raise a ValueError instead of silently ignoring the extra item?
Plain (non-starred) unpacking requires the item count to match exactly, by design, so it acts as a built-in shape check that surfaces bugs immediately rather than silently dropping data.
What is the difference between the * in def f(*args): ... and the * in f(*my_list)?
In a function definition, *args GATHERS extra positional arguments into a tuple inside the function. In a call, *my_list SPREADS an existing iterable out into separate positional arguments. Same symbol, opposite (gather vs. spread) roles.
Given d1 = {'a': 1, 'b': 2} and d2 = {'b': 99, 'c': 3}, what does {d1, d2} produce, and why?
{'a': 1, 'b': 99, 'c': 3}. Keys are merged in the order the ** expressions appear, and a later source overwrites an earlier one on a duplicate key — d2's 'b': 99 wins over d1's 'b': 2.
Write the one-line idiom to swap the values of a and b without a temporary variable.
a, b = b, a — Python builds the right-hand tuple from the current values of b and a before rebinding the left-hand names, so both swaps happen atomically from the code's perspective.
Can more than one starred target appear in the same unpacking assignment, e.g. *a, b, *c = [1, 2, 3]?
No — Python allows at most one starred target per unpacking assignment; using two raises SyntaxError: multiple starred expressions in assignment.
What does unpacking a plain string, e.g. a, b, c = 'xyz', actually bind?
a='x', b='y', c='z' — a string is iterable character by character, so unpacking it splits it into its individual one-character strings, not into words or a single chunk.
Write a function first_and_rest(items) that returns the first element and a list of the remaining elements, using unpacking. Return (None, []) for an empty list.
def first_and_rest(items: list) -> tuple: if not items: return None, [] first, *rest = items return first, rest print(first_and_rest([1, 2, 3])) # (1, [2, 3]) print(first_and_rest([])) # (None, [])
Guard the empty case first (starred unpacking of an empty list would still work and give first no value to bind, raising ValueError), then use first, *rest = items to split the head from the tail in one step.
What does this print, and why doesn't it raise a ValueError even though there are three targets for one item?
It prints 10 [] 10. The starred target *b is allowed to match zero items, and the non-starred targets a and c must each still get one item — with only one element available, both a and c bind to that same single element 10, leaving b as an empty list.
Write a function merge_configs(*dicts) that merges any number of dicts into one, with later dicts overriding earlier ones on key collisions, using dict unpacking.
def merge_configs(*dicts: dict) -> dict: merged: dict = {} for d in dicts: merged = {merged, d} return merged print(merge_configs({"a": 1}, {"a": 2, "b": 3}, {"b": 4})) # {'a': 2, 'b': 4}
Each iteration spreads the running merged dict and the next dict into a fresh dict literal; because d is applied after merged, d's keys win on any collision, giving last-writer-wins semantics across all arguments.
What does this print?
(1, 2, 3) {'verbose': True, 'debug': False} — *data spreads to positional arguments 1, 2, followed by the literal positional 3; **opts spreads to a keyword argument, followed by the literal keyword debug=False. Positional and keyword unpacking can be freely mixed with literal arguments as long as all positionals precede all keywords.
a, *b, c = [10]
print(a, b, c)def show(*args, **kwargs):
print(args, kwargs)
data = (1, 2)
opts = {"verbose": True}
show(*data, 3, **opts, debug=False)