What does the Python call f(*a, *b, **d1, **d2) pass to f?
answer
- One call, several unpackings
- Star spreads, double star merges
- Positional tuple follows source order
- PEP 448, landed in 3.5
- Star wants an iterable, double star a mapping
basics
~20 sBoth iterables are flattened into one positional argument list, in source order, and both mappings are merged into the keyword arguments. Python 3.5 (PEP 448) allowed more than one star and double-star unpacking per call.
solid answer
~40 sIn a call, `*` spreads any iterable out as separate positional arguments and `**` spreads any mapping out as separate keyword arguments. Before Python 3.5 a call could carry at most one of each; PEP 448 lifted that, so `f(*a, *b, **d1, **d2)` is one call whose positional tuple is the items of `a` followed by the items of `b`, and whose keyword bindings come from `d1` then `d2`. Plain positional arguments may be interleaved (`f(*a, 99, *b)` is legal) and the source order is the resulting order. The same syntax builds displays: `[*a, *b]`, `(*a, *b)`, `{*s1, *s2}`, `{**d1, **d2}`. `*` only needs an iterable; `**` needs a mapping, and anything else raises `TypeError` at the call.
code
python · 7 linesdef f(*args, **kwargs):
return args, kwargs
a = [1, 2]
b = (3,)
print(f(*a, *b, **{'x': 1}, **{'y': 2}))
print(f(*a, 99, *b))go deeper
Be ready to read a call like f(*a, *b, **d1, **d2) aloud and say what f receives: one flat positional tuple in source order, and keyword arguments drawn from both mappings.
Explain the mechanics: the tuple and dict are built at the call site before the callee runs, * iterates its operand while ** requires a mapping, and the same syntax builds list, set, tuple and dict displays.
Show judgement about where unpacking belongs in an API: forwarding layers that unpack blindly lose signature checking, and unpacking a large mapping on a hot path allocates a fresh dict on every call.
Own the interface question — whether a public function should accept spread arguments at all, versus one explicit options object, and what that choice costs in typing, validation and evolution of the call contract.
## Two directions, one syntax - A star in a *call* spreads: `f(*a)` means "iterate `a` and hand each item to `f` as its own positional argument". - A double star spreads a mapping: `f(**d)` means "for every entry of `d`, pass a keyword argument of that name". The same characters in a *definition* do the opposite job — they collect leftovers into a tuple and a dict — and that mirror image is the first thing to keep straight, because the two are written identically and read in opposite directions. ## What PEP 448 changed Until Python 3.4 a call could carry at most one `*` and at most one `**`, and neither could appear inside a list, set, tuple or dict display. PEP 448, "Additional Unpacking Generalizations", shipped in **Python 3.5** and removed both restrictions. Since then `f(*a, *b, **d1, **d2)` is a single, ordinary call, and `[*a, *b]`, `(*a, *b)`, `{*s1, *s2}` and `{**d1, **d2}` are ordinary displays. Nothing about this has changed through Python 3.14. ## How the call is assembled The compiler walks the argument list left to right. 1. Every plain positional argument contributes one item; every `*` operand is iterated to exhaustion and contributes its items in iteration order. 2. The result is one flat tuple, built at the call site, before `f` runs. 3. Then every explicit `name=value` and every entry of a `**` operand is bound as a keyword. `f` itself sees nothing special: it receives an ordinary positional tuple and ordinary keyword bindings, and it cannot tell whether the caller wrote them out literally or unpacked them. ## Ordering rules the grammar enforces - A plain positional argument may follow an iterable unpacking — `f(*a, 99, *b)` puts `99` between the two groups — - and a keyword argument may sit between two star unpackings, so `f(*a, x=1, *b)` compiles. - What is not allowed is an iterable unpacking *after* a keyword unpacking: `f(**d, *a)` is a `SyntaxError` reading "iterable argument unpacking follows keyword argument unpacking". One more grammar limit surprises people: `[*x for x in y]` is a `SyntaxError` ("iterable unpacking cannot be used in comprehension"); PEP 448 floated that form and it was not accepted. ## What each operand must be - `*` accepts any *iterable* — a list, a tuple, a string, a `range`, a generator object, a file object, a set. `f(*d)` where `d` is a dict passes the **keys**, because iterating a dict yields keys. A non-iterable raises `TypeError: argument after * must be an iterable, not int`. - `**` is stricter: the operand must be a *mapping*, and `f(**[1, 2])` fails with "argument after ** must be a mapping, not list". Beyond that, the keys of a `**` operand must be strings, because they become keyword names: `f(**{1: 'a'})` raises `TypeError: keywords must be strings`. That restriction belongs to the call form only — the dict display `{**{1: 'a'}}` is perfectly happy, because it is building a dict rather than binding parameters. ## The display forms `[*a, *b]` builds a new list from any two iterables, which is why it succeeds where `a + b` fails: `+` on sequences demands operands of the same type, while `*`-unpacking only demands iteration, so `[*some_list, *some_generator]` is fine. - `(*a, *b)` builds a tuple, - `{*s1, *s2}` builds a set (deduplicating across both), - and `{**d1, **d2}` builds a dict. Note the brace asymmetry: `{*x}` is a set display and `{**x}` is a dict display, while bare `{}` is an empty dict — Python has no empty-set literal. ## Nothing is copied deeply Unpacking rebinds references. The tuple and dict that the call builds are new containers, but the objects inside them are the very objects that were in `a`, `b`, `d1` and `d2`. If a value is a mutable list, the callee mutating it mutates the caller's data; use `copy.deepcopy` at the boundary if that matters. ## Why it earns its place Unpacking is how: - adapters forward arguments they do not want to enumerate, - how a layered configuration is assembled (`f(**defaults, **overrides)` — provided the keys are disjoint), - and how a fixed prefix is combined with a computed tail (`f(session, *computed_args)`). It is not free: each call materialises a fresh tuple and dict, so unpacking a very large mapping on a hot path is a real allocation, not a syntax-only nicety.
- May a plain positional argument appear after a star unpacking in the same call?Yes, since Python 3.5. `f(*a, 99, *b)` is legal and the positional tuple follows source order, so `99` lands between the items of `a` and the items of `b`. A keyword argument between two star unpackings is legal too. The one ordering the grammar rejects is an iterable unpacking after a keyword unpacking: `f(**d, *a)` is a SyntaxError.
- What must the operand of `*` and of `**` be, and what error do you get otherwise?`*` needs any iterable — list, tuple, string, range, set, generator object, file object; a dict unpacks to its keys. A non-iterable gives `TypeError: argument after * must be an iterable`. `**` needs a mapping; a list gives `TypeError: argument after ** must be a mapping`. In a call the mapping's keys must also be strings, or you get `TypeError: keywords must be strings`.
- Does `[*a, *b]` copy the elements?No. It builds a new list, but the elements are the same objects that were in `a` and `b` — a shallow copy of the containers only. Mutating a nested list reached through the new list is visible through the original. Use `copy.deepcopy` when the caller and callee must not share nested state.
A star unpacking is a shipping pallet unwrapped at the door: the parcels go through one by one in the order they were stacked, and two pallets simply unload one after the other.
saying these in an interview costs you the question
- Claims only one star and one double star are allowed per call
- Thinks star unpacks mappings and double star unpacks sequences
- Says [*x for x in y] is valid comprehension syntax
- Believes star requires a list or tuple rather than any iterable
- Assumes unpacking deep-copies the values it spreads
- Confuses call-site unpacking with collecting *args in a signature