skip to content

Why does calling `def f(a, b)` as `f(1, a=2)` raise a TypeError?

level: middleimportance: should knowfreq 45%

answer

  1. Two arguments competing for one slot
  2. Order of the steps, not of the call
  3. Positionals are bound before keywords
  4. The first slot was already filled
  5. TypeError: got multiple values for argument

basics

~20 s

Positional arguments bind first, left to right, so the 1 already fills a. The keyword a=2 then asks for a slot that is taken, and Python raises TypeError: f() got multiple values for argument 'a'.

solid answer

~40 s

Argument binding happens in a fixed sequence. Positional arguments are assigned to the positional parameters left to right, then keyword arguments are matched to the remaining parameters by name, then defaults fill whatever is still empty. In `f(1, a=2)` the first step gives `a` the value `1`, so the second step finds `a` already bound and raises `TypeError: f() got multiple values for argument 'a'`. That message is specific: it means a name collided with a slot a positional argument had already filled, which is different from `got an unexpected keyword argument` (the name is not in the signature at all) and from `missing 1 required positional argument` (nothing filled the slot). Marking the parameter positional-only with `/` changes the outcome to a different, clearer TypeError.

code

python · 8 lines
python
def f(a, b):
    return a, b

for call in ("f(1, a=2)", "f(1, 2, 3)", "f(b=2)", "f(1, 2, c=3)"):
    try:
        eval(call)
    except TypeError as e:
        print(call, "->", e)

go deeper

for a junior

Read the traceback literally: 'got multiple values for argument a' means you supplied a twice, once by position and once by name. Drop one of them.

for a middle

Explain the binding sequence in order — positionals left to right, then keywords by name, then defaults — and map each of the four common TypeError messages onto the step that produced it.

for a senior

Show how you diagnose it in someone else's stack: which layer supplied the positional argument, whether a signature was renamed or reordered, and which marker would have made the call impossible to write.

for a principal

Frame it as an API-surface question: a positional-or-keyword parameter carries two promises at once, and deciding where the markers go removes an entire class of call-site error from the codebase.

## The binding sequence A call is not matched to a signature all at once; CPython works through a fixed sequence, and nearly every argument-related `TypeError` you will be asked to explain falls out of one step of it. 1. **Positional arguments fill positional slots, left to right.** The first positional argument goes to the first positional-or-keyword (or positional-only) parameter, the second to the next, and so on. Keyword-only parameters — anything after a bare `*` — are not part of this sequence at all. 2. **Keyword arguments are matched by name** to parameters that are still unfilled. 3. **Defaults fill whatever remains empty.** 4. **Anything left over is an error.** `f(1, a=2)` against `def f(a, b)` fails at step 2. Step 1 has already bound `a = 1`; the keyword then names a slot that is occupied, and Python reports: ``` TypeError: f() got multiple values for argument 'a' ``` Nothing about this is special to duplicated *values* — `f(1, a=1)` fails identically. The complaint is about the slot, not the value. ## The error family, and what each one tells you The messages are distinct on purpose, and reading them precisely turns a puzzling call into a two-second fix. For `def f(a, b)`: * `f(1, a=2)` → **got multiple values for argument 'a'** — a positional argument and a keyword argument competed for the same slot. * `f(1, 2, 3)` → **takes 2 positional arguments but 3 were given** — step 1 overflowed. The count is the size of the positional region only, so a signature with keyword-only parameters reports a smaller number than the total parameter count, which surprises people. * `f(b=2)` → **missing 1 required positional argument: 'a'** — step 3 left a slot with no value and no default. * `f(1, 2, c=3)` → **got an unexpected keyword argument 'c'** — step 2 had a name that matches no parameter. And with the markers this leaf is about: for `def f(a, *, b)`, `f(1)` gives **missing 1 required keyword-only argument: 'b'**, while for `def f(a, b, /)`, `f(1, a=2)` gives **got some positional-only arguments passed as keyword arguments: 'a'**. The word "keyword-only" or "positional-only" in a traceback is a direct pointer at a marker in the signature. ## Why a real call ends up looking like this Two situations produce it in practice. The first is a partially-applied or wrapped call: a helper already supplies the first argument positionally, and a caller further out also supplies it by name, so the two meet in the middle. The second is a signature change — a parameter is renamed or reordered, and a call that named it now collides with a positional argument that has shifted along. Both are ordinary consequences of a signature having *two* public promises: the order of the positional parameters, and the names a caller is permitted to use. ## Where the markers change the outcome That is exactly what `/` and `*` are for. If the leading parameter is declared positional-only — `def f(a, b, /)` — a caller cannot name it, so `a=2` is no longer a competing binding; it is simply illegal, and the error says so in terms of the marker. If instead the trailing options are keyword-only — `def f(a, *, b)` — no positional argument can ever reach them, so no positional argument can ever collide with a name. A useful way to hold it: **a name-versus-position collision is only possible in the positional-or-keyword region.** Parameters that are positional-only or keyword-only are, by construction, immune to the whole error class, and a signature that pushes its parameters to one side or the other has fewer ways to be called wrongly. ## Two details worth knowing Keyword arguments are matched purely by name, so their order in the call is irrelevant: `f(b=2, a=1)` and `f(a=1, b=2)` are the same call. Repeating a name in one call — `f(a=1, a=2)` — is caught earlier still, at compile time, as `SyntaxError: keyword argument repeated: a`, because no binding step is needed to know it is wrong.

  • How does 'got multiple values for argument' differ from 'got an unexpected keyword argument'?
    The first means the name exists in the signature but its slot was already filled by a positional argument — a collision. The second means no parameter of that name exists at all, so the keyword had nowhere to go. One is a duplication problem, the other a spelling or version problem, and confusing them sends you looking in the wrong place.
  • Does the order in which keyword arguments appear in a call matter?
    No. Keyword arguments are matched by name, so `f(b=2, a=1)` and `f(a=1, b=2)` are the same call. Only positional arguments are order-sensitive. Repeating a name in a single call is rejected before the call even runs, as `SyntaxError: keyword argument repeated: a`.
  • Why does the message for `def f(a, *, b)` called as `f(1, 2)` say the function takes one positional argument?
    Because the count reports the size of the positional region only, and `b` sits after the bare `*`, so it is keyword-only and never eligible for positional binding. A signature with keyword-only parameters therefore reports a smaller number than its total parameter count, which is a common source of confusion when reading the traceback.

Positional arguments take their seats first, in order; a keyword argument that arrives later and asks for seat a by name finds someone already sitting in it.

saying these in an interview costs you the question

  • Says keyword arguments are bound before positional ones
  • Thinks the error is about two different values
  • Confuses it with 'unexpected keyword argument'
  • Blames a missing default value for the failure
  • Claims the order of keyword arguments matters
  • Believes only forwarding code can trigger it

context