1.2.28 · D5Introduction to Programming (Python)

Question bank — Default parameters, keyword arguments

1,820 words8 min readBack to topic

Warm-up pictures (build the mental model first)

Before the traps, three drawings you will keep pointing back to.

1 — Parameters vs arguments as a binding map. A parameter is an empty labelled box inside the function; an argument is a value that gets poured into that box when you call. Look at the arrows: the call values on the left flow into the parameter boxes on the right, and the box names are the labels you can address by keyword.

Figure — Default parameters, keyword arguments

2 — Why "defaults drift right". The phrase defaults drift right just means: in a definition, every parameter WITHOUT a default must appear before any parameter WITH one. The figure shows why. Python fills boxes strictly left to right by position. If a default box sat on the left and a required box on the right, a single positional value would have no rule telling it which box to enter — the ✗ layout is ambiguous, so Python forbids it outright.

Figure — Default parameters, keyword arguments

3 — The one-object-shared trap. For def add(item, bag=[]), the list [] is built ONCE, at the moment the def line runs — not on each call. The red list object is the same object every call reaches for, so appends pile up in it. The figure traces the arrows: three calls, one shared red list.

Figure — Default parameters, keyword arguments

4 — Evaluation timing: def-time vs call-time. The timeline shows the default expression being evaluated at the ★ marked "def runs" — well before any call. Reassigning an outside variable after that ★ cannot change what was already frozen into the default.

Figure — Default parameters, keyword arguments

5 — Defaults and DRY, concretely. Without a default, four call sites all repeat 5432, "utf8", 30; change the port once and you must hunt down every copy. Baking the value into the definition means one place to edit. The figure contrasts the two worlds — the red single source of truth vs the scattered duplicates.

Figure — Default parameters, keyword arguments

See DRY principle - Don't Repeat Yourself for the general rule this illustrates.


True or false — justify

A default parameter is one that carries a fallback value like def f(x=10). True or false?
TRUE — that IS the definition: a parameter given a fallback in the def, used only when the caller omits it. Everything below builds on this.
The default value of a parameter is created fresh every time the function is called.
FALSE — it is created once, when the def line runs (see figure s03/s04). That single object is reused on every call, which is exactly what causes the mutable-default trap.
In a definition, you may write def f(a=1, b).
FALSE — non-default parameters must come before default ones (defaults drift right, figure s02). Otherwise a bare positional argument could not tell whether it fills a or b.
box(height=5, width=2) and box(width=2, height=5) call the same function identically.
TRUE — keyword arguments are matched by name, so their order is irrelevant; both bind width=2, height=5.
Once you use one keyword argument in a call, every argument after it must also be a keyword.
TRUE — all positional arguments must come before any keyword argument, so after the first keyword Python can no longer resolve a bare positional by slot.
Using None as a default means the parameter can never hold a list.
FALSE — None is only a sentinel saying "nothing was passed"; inside the function you replace it with a real list (if bag is None: bag = []), and callers may still pass their own list.
A parameter and an argument are two words for the same thing.
FALSE — a parameter is the name in the definition; an argument is the value supplied in the call (figure s01). def f(x) has parameter x; f(5) has argument 5.
def connect(host, port=5432) forces every caller to type 5432.
FALSE — the whole point of a default is that callers who want 5432 type nothing; only the unusual caller overrides it.

Spot the error

Each line is broken or surprising. Name the exact fault.

def f(x=1, y): ... — what breaks and when?
SyntaxError at definition time: a non-default parameter y follows a default x (the ✗ layout in figure s02). Python rejects the def before it ever runs.
greet("Asha", name="Asha") where def greet(name, greeting="Hello") — what happens?
TypeError: got multiple values for argument 'name' — the positional "Asha" already filled name, and the keyword tries to fill it again.
f(a=1, 2) in a call — why is this illegal?
SyntaxError / positional-after-keyword: once a=1 is a keyword, the bare 2 has no slot Python can safely assign it to. Positionals must come first.
def add(item, bag=[]): bag.append(item); return bag — what is the hidden bug?
The list [] is created once at def time (figure s03), so every call sharing the default accumulates into the same list: add(1)[1], then add(2)[1,2].
def collect(x, seen=None): if seen == []: seen = []; ... — why is == [] the wrong test?
== [] also matches a real empty list the caller legitimately passed, so you'd silently throw away their object. Use is None, which asks "was nothing passed?".
power(5, mod=7, 2) where def power(base, exp=2, mod=None) — what fails?
The bare 2 comes after the keyword mod=7, violating positional-before-keyword; it's a syntax error, not a logic bug.
def f(a, b, /, c, *, d): ... then f(1, 2, 3, 4) — why does the call fail?
d is keyword-only (after the *), so it must be d=4; the bare 4 cannot fill it. TypeError: too many positional arguments.

Why questions

Explain the reason, not just the rule.

Why must defaults come after non-defaults in a definition?
So position stays unambiguous: Python fills parameters left-to-right (figure s02), and if a default sat first, a lone positional couldn't tell whether it fills that slot or skips to the next.
Why must positionals come before keywords in a call?
After a keyword like d=9, Python has stopped counting slots by position, so a later bare value has no place to land — the resolution rule would break.
Why do keyword arguments matter when a function has many parameters?
They make the call self-documenting and order-independent, so box(width=2, height=5) reads clearly instead of a mystery like box(2,5,0,1).
Why does the mutable-default bug exist at all — why not evaluate the default per call?
The default expression is evaluated when def executes to build the function object (figure s04); re-evaluating per call would be a different, more expensive design. Immutable defaults hide the sharing; mutable ones expose it. See Mutable vs immutable objects.
Why is None the standard sentinel instead of, say, [] or 0?
None is a unique singleton that no meaningful data usually equals, so is None cleanly distinguishes "omitted" from "genuinely passed an empty/zero value".
Why do defaults connect to the DRY principle?
They bake the common value into one place (figure s05) instead of forcing every caller to repeat it, so a change happens once rather than in dozens of call sites — the essence of DRY principle - Don't Repeat Yourself.

Edge cases

Boundary and degenerate scenarios — cover every corner.

Can a caller pass an empty list to a bag=None function and have it used?
Yes — collect(1, seen=[]) passes a real empty list; since it's not None, the function appends into that list and returns it, respecting the caller's object.
What if two calls to add(item, bag=None) each omit bag — are their lists independent?
Yes — each call hits bag is None and builds a fresh [], so the lists are separate. That is exactly the fix for the shared-mutable trap.
Is a tuple default like def f(x=()) dangerous the way [] is?
No — a tuple is immutable, so even though it's shared across calls, nobody can mutate it; the shared object can never accumulate state.
Can every parameter have a default?
Yes — def f(a=1, b=2) is legal, and f() calls it with all defaults. Rule 1 is only violated when a non-default follows a default.
What happens if you call power(5) where def power(base, exp=2, mod=None)?
Both defaults apply: exp=2, mod=None, giving 5**2 = 25 with no modulo — the tiny common-case call the defaults were designed for.
Can you set a later parameter while keeping an earlier one at its default?
Yes, but only by keyword: power(5, mod=7) skips exp (stays 2). Positionally it's impossible — you can't leave a hole — which is precisely why keyword arguments exist.
Does giving a default change whether the parameter is local to the function?
No — defaults don't affect scope; the parameter is still a local name inside the function body. See Variable scope - local vs global.
What if a default references a variable defined outside — when is that value captured?
At def time (figure s04). x=5; def f(a=x): ... freezes a's default to 5; later reassigning x does not change the default, because the expression was already evaluated.
In def f(a, b, /):, what does the / do to defaults and keywords?
Everything before / is positional-only: you may still give those parameters defaults, but callers can NEVER pass them by name. f(a=1) raises TypeError.
In def f(*, key, mode="x"):, what does the leading * force?
Everything after * is keyword-only: key and mode can only be passed by name (f(key=1)), never positionally — even key, which has no default, must be named.
Can a keyword-only parameter still carry a default?
Yes — def f(*, mode="x") gives mode a default AND makes it keyword-only. Omit it and the default applies; supply it only as mode=....

Connections