Defaults
A parameter with a default becomes optional:
def greet(name, greeting="Hello", excited=False):
Callers supply what they care about and ignore the rest. Defaults must come after all non-default parameters, for the same reason: otherwise a positional call would be ambiguous.
The mutable default trap
This looks reasonable and is not:
def add_item(item, basket=[]):
basket.append(item)
return basket
Call it three times with no basket and you get one item, then two, then three — all in the same list. The reason is a single rule with a large consequence: default values are evaluated once, when the def runs, not on each call. That one list is created at definition time and reused forever, so every mutation accumulates.
The second program on this page prints the id of the default object before and after a call, to show it really is the same object rather than a new one that happens to have old contents.
The fix is idiomatic and worth memorising:
def add_item(item, basket=None):
if basket is None:
basket = []
None is immutable, so there is nothing to accumulate, and the fresh list is built inside the call where it belongs.
Immutable defaults — numbers, strings, True, None, tuples — are all safe. The rule is simply: never default a parameter to a list, dict or set.
Keyword-only parameters
Anything after a bare * in the definition can only be passed by name:
def connect(host, *, timeout=30, retries=3):
...
connect("db.local", timeout=5) # fine
connect("db.local", 5) # TypeError
This is how you stop a call site from becoming a row of unlabelled values. connect("db.local", 5, 2) tells a reader nothing; forcing the names makes the call self-documenting, and it means you can reorder or add options later without breaking anyone.
The mirror image is /, which marks parameters as positional-only. It appears in the standard library more than in application code.
Arguments are evaluated at the call
def log(message, when=now()): # now() runs ONCE, at definition
This is the same rule as the mutable default, wearing different clothes. If you want the current time on each call, compute it inside:
def log(message, when=None):
if when is None:
when = now()
Any expression in a default - a function call, a list, a dict, an object - is evaluated once when the def statement runs, and the result is reused forever.
Too many parameters is a design signal
A function with eight parameters is hard to call correctly and hard to change. Two usual remedies:
- group related arguments into a small object or a dataclass, so
draw(config) replaces draw(x, y, w, h, colour, border, alpha, z) - split the function, because eight parameters often means it is doing more than one job
Neither is a rule, but if you are counting arguments on your fingers at the call site, the call site is telling you something.
Argument order in the definition
Positional first, then defaults, then *args, then keyword-only, then **kwargs. Python enforces most of that, and the error messages are clear when you get it wrong - unlike the mutable default, which never complains at all.
What the signature communicates
A function signature is the part other people read most and change least. It is worth treating as an interface rather than an accident of how the function grew.
Parameters with no default are requirements: the caller must supply them and the function cannot work without them. Parameters with defaults are options, and the default should be the choice that is right most of the time. If you find yourself writing a default that callers nearly always override, the default is wrong and it is quietly making every call site longer.
Order matters for reading as much as for the language. Put the thing the function is about first - the data, the object, the path - and the modifiers after it. resize(image, width=800) reads correctly; resize(width=800, image=img) is legal and reads backwards.
Passing arguments through
When one function calls another and wants to forward whatever it was given, the star forms do it without repeating the signature. That is the right tool for wrappers, decorators and thin adapters, where repeating the parameters would mean updating two places every time the inner function changes.
It is the wrong tool for a function people call directly. def process(*args, **kwargs) gives the caller no idea what to pass, gives the editor nothing to complete, and turns a mistake that would have been a clear TypeError into a KeyError somewhere deeper. Be explicit at the surface and flexible only where you are genuinely forwarding.
Mutable arguments, not just mutable defaults
The default-argument trap gets the attention, but the same underlying fact - that arguments are passed by reference to the same object - applies to every call. A function that appends to a list it was given has changed the caller's list. Sometimes that is the point and should be said in the name: add_item, update_config, sort_in_place. When it is not the point, copy at the top of the function or return a new object, and let the caller decide.
Watching the default accumulate
The trap is more convincing when the shared object is visible:
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item("a"))
print(add_item("b"))
print(add_item("c"))
print("the default itself:", add_item.__defaults__)
['a']
['a', 'b']
['a', 'b', 'c']
the default itself: (['a', 'b', 'c'],)
The last line is the point. __defaults__ holds the actual default values, and the list stored there has grown — it is not that each call somehow gets the previous result, it is that there has only ever been one list, created when def ran, and every call has been appending to it.
Nothing about this is a special rule for functions. basket=[] is an expression evaluated once, at definition time, exactly like x = [] at module level. The surprise comes from expecting the default to be re-evaluated on each call, which nothing in the syntax promises.
The fix, once more, because it is worth having in your fingers:
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
None cannot accumulate anything, and the list is built inside the call where it belongs.
How a call is matched to a signature
Understanding the binding order makes the error messages readable, and there are only four steps.
Positional arguments are assigned to parameters left to right. Then keyword arguments are matched by name to whatever is left. Then any parameter still unfilled takes its default. Then, if a parameter is still unfilled and has no default, the call fails.
That order explains the errors. missing 1 required positional argument: 'x' means step four found a gap. got multiple values for argument 'x' means a positional argument filled x in step one and a keyword tried to fill it again in step two — which is what happens when you pass the first argument positionally *and* by name. got an unexpected keyword argument 'timeuot' means step two found no parameter of that name, which is the typo case, and is exactly the error that **kwargs would have swallowed.
Two rules follow from the same ordering. In a call, positional arguments must come before keyword ones, because Python cannot tell where a positional one belongs once names have started. And in a definition, parameters with defaults must come after those without, for the same reason from the other side.
Saying what a parameter expects
Type hints do not convert or enforce anything at runtime, and they are still the cheapest documentation a signature can carry:
def greet(name: str, times: int = 1) -> str:
return f"hello {name} " * times
The value is threefold. A reader learns what to pass without reading the body. An editor can complete and check the call. And a type checker run in CI catches the mismatch before it ships — which is the only one of the three that actually prevents anything.
Two conventions are worth adopting with them. Annotate the public functions and skip the obvious internal ones; annotations on a two-line helper cost more to read than they explain. And when a parameter can be None, say so — def find(name: str) -> User | None — because that is the case callers most often forget and the one a checker most usefully flags.
Hints also document the mutable-default fix nicely: basket: list | None = None states both the type and the fact that omitting it is allowed, which is exactly what the None sentinel means.
Choosing the default
A default is a decision about what most callers want, and getting it wrong is quiet: nothing fails, every call site just gets a little longer.
The test is simple. If most calls override the default, the default is wrong. Either it should be the value people actually pass, or the parameter should be required so that callers have to think about it. A default that exists only because the parameter felt like it needed one adds a choice without making one.
Defaults should also be safe rather than convenient. A timeout=None meaning "wait forever" is a default that turns a slow dependency into a hung program; a finite timeout is the better choice even though it can fail. The same reasoning applies to overwrite=False, strict=True and retries=0: when in doubt, default to the behaviour whose failure is loud.
And a default should not depend on the state of the world. Anything evaluated at definition time — the current directory, the time, an environment variable, a config object — is frozen at import and will be wrong for any program that changes it afterwards. None plus a lookup in the body reads the value when the call happens, which is nearly always what was meant.
Finally, prefer a default over an optional parameter that changes the return type. A function that returns a number normally and a tuple when verbose=True has two signatures pretending to be one, and every caller has to know which it triggered.
Questions people ask
Are arguments passed by value or by reference? Neither, in the C sense. The function gets another name for the same object, so mutating it is visible to the caller and rebinding is not.
Can a default refer to another parameter? No. Defaults are evaluated at definition time, when no arguments exist. Use None and compute it in the body.
Why must defaults come last? Otherwise a positional call could not tell which parameter a value was for.
Is a tuple a safe default? Yes. Tuples are immutable, so there is nothing to accumulate.
How do I force a caller to use names? Put a bare * before those parameters in the definition.
Can I see a function's defaults? f.__defaults__ for positional ones and f.__kwdefaults__ for keyword-only ones.
How many parameters is too many? When you are counting on your fingers at the call site. Group them into an object, or split the function.
Can I make every parameter keyword-only? Yes, put the bare * first: def f(*, a, b). Callers must then name both.
Does the order of keyword arguments matter? Not to Python. They are matched by name, so any order works — though matching the signature's order helps a reader.
Can two parameters share a default object? They can, and they should not if it is mutable, because then both accumulate into the same thing.
Recap in one screen
- Arguments match by position first, then by name; positional ones come first in a call and non-default parameters come first in a definition.
- Defaults are evaluated once, when
def runs — never default to a list, dict or set. None plus a check in the body is the idiomatic fix, and it reads correctly in a type hint too.- A bare
* makes the parameters after it keyword-only, which is how a call site stops being a row of unlabelled values. - The signature is the part others read most; required parameters are requirements, and a default should be the right choice most of the time.