What do a Python function's __defaults__ and __kwdefaults__ hold, and when are they evaluated?
answer
- One tuple, one dict
- Filled when the def runs, not per call
- Aligned from the right for positional ones
- Keyword-only defaults are keyed by name
- The shared object sits inside the tuple
basics
~20 sdefaults is a tuple of default values for the trailing positional-or-keyword parameters, or None when there are none. kwdefaults is a dict of defaults for keyword-only parameters. Both are evaluated once when the def executes and stored on the function object.
solid answer
~40 sDefaults are not re-evaluated per call: the `def` statement evaluates each default expression once, builds the function object, and stores the results on it. `__defaults__` is a **tuple** covering the trailing positional-or-keyword parameters in order (or `None`); `__kwdefaults__` is a **dict** keyed by name covering keyword-only parameters (those after `*` or `*args`), or `None`. Because the values are stored objects, a mutable default such as `[]` is one shared list for the life of the function — appending to it inside the body is visible in `f.__defaults__` afterwards, which is exactly why the `None`-sentinel idiom exists. Both attributes are writable, so you can retune defaults at runtime by assigning a new tuple or dict, and introspection code reads them to report or re-apply defaults.
code
python · 9 linesdef add_row(row, rows=[], *, sep=","):
rows.append(row)
return sep.join(rows)
print(add_row.__defaults__, add_row.__kwdefaults__)
add_row("alpha")
print(add_row.__defaults__)
add_row.__defaults__ = ([],)
print(add_row("beta"))go deeper
Remember that default values are created once when the def line runs, so a list or dict default is shared by every call. Default to None and build the object inside the function.
Be ready to name both attributes with their types, say which parameter group each covers, explain the right-aligned tuple, and show the shared object through defaults rather than just asserting it.
Show the diagnosis: read defaults on a live function to prove a shared object is the cause, and recognise non-obvious frozen defaults such as a value computed at import time from locale or the clock.
Own it as a review rule rather than a trivia item: mutable or environment-derived defaults are a class of latent bug, and the team standard should be sentinel defaults plus a lint rule, not case-by-case vigilance.
### Two attributes, two parameter groups A Python function object stores its default values in exactly two places: - **`__defaults__`** — a tuple holding the defaults for parameters that can be passed positionally, aligned to the **last** N such parameters. Given `def add_row(row, rows=[], *, sep=",")`, `__defaults__` is `([],)`: only `rows` has one, and `row` is simply not represented. There are no placeholders, which is why you count from the right when matching values to names. - **`__kwdefaults__`** — a dict for keyword-only parameters, the ones declared after a bare `*` or after `*args`. For the same function it is `{'sep': ','}`. A dict is the right shape here because keyword-only parameters are matched by name, not by position, and any subset of them may have defaults. When there are no defaults in a group, the attribute is `None`, not an empty tuple or dict. Code that reads them must handle `None`. ### Evaluated once, at def time The crucial semantic is *when* those expressions run. The `def` statement is an executable statement: when the interpreter reaches it, it evaluates every default expression, in left-to-right order, in the enclosing scope, and hands the resulting objects to the function object being created. A default expression is never re-evaluated on a call. If a default is `datetime.date.today()`, every call for the process's lifetime reports the day the module was imported. If it is `[]`, there is exactly one list. This is what makes the shared-mutable-default behaviour so visible through reflection: call a function whose body appends to its list default, then print `f.__defaults__`, and the accumulated items are sitting right there in the tuple. The tuple itself never changed — tuples are immutable — but the list *inside* it did. The standard remedy is the sentinel: default to `None` and build a fresh object inside the body when the caller passed nothing. Interviewers like this question precisely because the attribute makes the mechanism observable instead of mysterious. ### They are writable Both attributes can be reassigned on a plain Python function. Assigning `f.__defaults__ = (0,)` really does change what a later call uses, and it is how you would reset a default that has been polluted. `__defaults__` must be assigned a tuple (or `None`) and `__kwdefaults__` a dict (or `None`); assigning the wrong kind raises `TypeError`. There is no length checking against the parameter list at assignment time — supply too many values and the extra ones simply shadow parameters further left, which makes runtime mutation a sharp tool. In practice it is used by test scaffolding and by libraries that retune a function they were handed, not by ordinary application code, where rebinding the module attribute to a `functools.partial` is clearer. ### What reads them Decorators and introspection layers read these attributes to report or reconstruct a call. A configuration-audit tool that prints every registered renderer alongside its effective defaults reads `__defaults__` and `__kwdefaults__` directly. Serialization of a function's configuration does the same. And note what a wrapper does to them: a decorator that returns an inner `def wrapper(*args, **kwargs)` has `__defaults__` of `None` and `__kwdefaults__` of `None`, because the wrapper declares no defaults of its own — and `functools.wraps` does **not** copy them, since they are not in `functools.WRAPPER_ASSIGNMENTS`. Anything reading defaults off a decorated function has to reach the wrapped function through `__wrapped__`. ### The failure this catches in review The realistic bug is a shared default that is not obviously mutable. A nightly report generator declaring `def render(rows, fmt=make_locale_format())` freezes a locale-dependent format object at import time; the format is then wrong for every run after the process's first locale change, and it is the same object for all 340 cases in a regression pack, so one case's in-place edit of it corrupts the rest. Reading `render.__defaults__` in a debugging session shows the single shared object immediately, which is often the fastest way to prove the diagnosis. The fix is the same sentinel discipline: default to `None`, build per call. ### The interview shape Name both attributes with their container types, say clearly that defaults are evaluated once at `def` time and stored, explain the shared-mutable consequence and the sentinel fix, and add that both attributes are writable and that a wrapper does not inherit them.
- Why is __defaults__ a tuple while __kwdefaults__ is a dict?Positional-or-keyword parameters are matched by position, and only a contiguous trailing run of them may have defaults, so an ordered tuple aligned from the right is enough. Keyword-only parameters are matched by name and any arbitrary subset of them may carry a default, so a name-keyed dict is the only representation that works.
- Does functools.wraps give a decorator's wrapper the wrapped function's defaults?No. `__defaults__` and `__kwdefaults__` are not in `functools.WRAPPER_ASSIGNMENTS`, so the wrapper keeps its own — typically `None`, since a `*args, **kwargs` wrapper declares none. Code that needs the real defaults has to follow `__wrapped__` back to the wrapped function.
- You inherit a function with a polluted mutable default. What do you do?Fix the definition: default to `None` and construct the value inside the body. As an emergency stopgap you can reassign `f.__defaults__` to a fresh tuple, which really does reset it, but that leaves the trap in place for the next caller and hides the bug from anyone reading the source.
The def statement is like printing a form with the defaults already typed in: every caller gets that same printed sheet, so anyone who writes on it changes what the next caller sees.
saying these in an interview costs you the question
- Says default expressions are re-evaluated on every call
- Expects __defaults__ to have an entry per parameter
- Thinks a mutable default is copied for each call
- Believes __defaults__ is read-only
- Confuses __kwdefaults__ with **kwargs collected at call time
- Assumes an empty tuple rather than None when there are no defaults