1.2.28 · D2Introduction to Programming (Python)

Visual walkthrough — Default parameters, keyword arguments

1,703 words8 min readBack to topic

Step 1 — A function is a row of empty boxes

WHAT. When you write def greet(name, greeting):, Python reserves two labelled boxes — one named name, one named greeting. These names are the parameters: the labels on the boxes in the definition.

WHY start here. Every rule later ("defaults go right", "positionals come first") is really a rule about how boxes get filled. If you picture the boxes, the rules stop being arbitrary.

PICTURE. Two empty boxes waiting. Nothing is in them yet — a bare definition fills nothing.

Figure — Default parameters, keyword arguments

Step 2 — Positional filling: values fall in left to right

WHAT. Call greet("Asha", "Hi"). Python pours the arguments into the boxes in order: the first value into the first box, the second into the second.

Each label under the code says which box that value lands in. Position — nothing else — decides.

WHY. This is the default behaviour Python always tries first. It is fast and needs no names, but it has a weakness we will expose in Step 4.

PICTURE. Two arrows, first value to box 1, second to box 2, strictly by order.

Figure — Default parameters, keyword arguments

Step 3 — A default is a value pre-printed inside a box

WHAT. Now write def greet(name, greeting="Hello"):. The second box arrives with "Hello" already printed inside it. That printed value is the default.

  • name — the term left of the = is the box label.
  • "Hello" — the value right of the = is what sits in the box if the caller drops nothing there.

WHY. This is the whole point of defaults (the DRY idea): the common value lives in one place. Callers who are happy with "Hello" say nothing; only the unusual caller overwrites it.

PICTURE. Box 1 empty and required; box 2 already showing a faint "Hello" that a real argument can paint over.

Figure — Default parameters, keyword arguments

Step 4 — Why position alone fails, and keywords rescue it

WHAT. Give a function three boxes: def box(width, height, x=0, y=0):. Read the call box(2, 5, 0, 1). Which number is which? Position forces you to remember an invisible order.

A keyword argument fixes this: you name the box out loud — x=0 means "put 0 in the box labelled x, wherever it is."

Because each value carries its own label, order stops mattering: box(height=5, width=2) fills the exact same boxes.

WHY this tool and not another. Position is compact but fragile — one slipped value and every later value is wrong, silently. Naming trades a few keystrokes for calls that are self-documenting and order-independent. That is the entire justification for keyword arguments.

PICTURE. Left: four bare numbers with question marks over the boxes. Right: the same call written with names, each arrow landing on its named box with certainty.

Figure — Default parameters, keyword arguments

Step 5 — The skip trick: reach a far box without touching the near ones

WHAT. With def power(base, exp=2, mod=None):, suppose you want to set mod but leave exp at its default 2. Positionally impossible — to reach the 3rd box by position you must first pour something into the 2nd. Keyword to the rescue:

  • 5 — positional, lands in base.
  • mod=7 — named, leaps over exp, so exp keeps its printed default 2.

Result: , then .

WHY. This is the day-to-day payoff of defaults + keywords together: change only the one knob you care about and let every other box keep its factory value.

PICTURE. Three boxes; an arrow to base, a curved arrow hopping over exp to land on mod, with exp still glowing its default 2.

Figure — Default parameters, keyword arguments

Step 6 — The two iron rules, seen as box logic

WHAT. Two rules, each obvious once you see the boxes.

WHY Rule 1. Python fills by position left to right. If a pre-filled box sat before an empty required box, a single positional value would be ambiguous — does it overwrite the default, or is it meant for the required one behind it? Banning that ordering deletes the ambiguity.

WHY Rule 2. The moment you write c=9, Python stops counting positions. A later bare 2 now has no known slot. So every position-based value must be resolved first.

PICTURE. Top row: a legal definition (empty boxes then pre-filled boxes) with a green check; a broken one with a red cross where a bare value can't tell which box it belongs to.

Figure — Default parameters, keyword arguments

Step 7 — The degenerate case: when the default is a mutable box-filler

WHAT. Here is the subtle one. The value printed in a default box is created once, the instant the def line runs — not fresh on each call. If that value is a list (a mutable object, one you can change in place), every call that omits the argument shares the same list.

def add(item, bag=[]):   # this [] is made ONCE, at def time
    bag.append(item)
    return bag
add(1)   # [1]
add(2)   # [1, 2]   <- same list, still holding the old item!

WHY it happens. The printed default is a single object. append mutates that one object, so the "leftovers" survive into the next call. This is why the concept only bites with mutable defaults — a number or string can't be mutated in place, so it never leaks.

PICTURE. One list object drawn once at definition; three separate calls all pointing arrows at the same box, each append piling on top of the last.

Figure — Default parameters, keyword arguments

The one-picture summary

Everything above collapses to one flow: arguments in, boxes fill by position, then by name, defaults cover the gaps — and beware the shared mutable box.

Figure — Default parameters, keyword arguments
Recall Feynman: tell the whole walkthrough plainly

A function is a row of labelled boxes. When you call it, your values fall into the boxes left to right — that's positional. Some boxes come with a value already printed inside (a default), so if you drop nothing there, the printed value stays. When there are many boxes, remembering the order is risky, so you can shout each value's box name — keyword arguments — and now order stops mattering, and you can even skip over a box to fill a far one while leaving the near one at its factory setting. Two rules keep it unambiguous: in the definition, empty required boxes go before pre-filled ones; in the call, all order-based values go before any named ones. And one trap: the printed default is stamped once, so if it's a list you keep adding to, everyone shares the same list — fix it by printing None and building a fresh list inside.


Active recall

Where do arguments land when you call with no names?
In boxes left to right, by position.
What does greeting="Hello" in a def line do?
Pre-fills that box with "Hello" unless the caller overwrites it.
How do you set a far box while keeping a nearer default?
Name the far box, e.g. power(5, mod=7) — the keyword hops over exp.
Why must positional arguments come before keyword ones in a call?
Once a name is used, Python stops counting positions, so a later bare value has no known slot.
When is a default value created?
Once, at definition time — not per call.
Why does the mutable-default bug only hit lists/dicts?
They are mutable — the single shared object is changed in place; numbers and strings can't be.

Connections