Mutable defaults keep state between Python calls
A default argument is evaluated when the function is defined, so one list can be reused across calls.
DoesItHold Editorial · · reviewed · guide-python-defaults-001
Direct answer
Use None as the sentinel and allocate the mutable value inside the function.
Python evaluates default expressions once, when the def statement runs. A list, dictionary or set created there becomes part of the function object and is reused whenever the caller omits that argument. Mutating it changes what a later call receives.
The surprising state is therefore deterministic, not random. Reproduce it with two calls that omit the argument, then compare both returned values and their identities. That small experiment separates a shared-default defect from state stored elsewhere.
- 01Definitionone default list is allocated
- 02Call Athe shared list becomes ['a']
- 03Call Bthe same list becomes ['a', 'b']
- 04SentinelNone creates a fresh list per call
Minimal example
Scrollable code region
def collect(value, items=[]):
items.append(value)
return itemsdef collect(value, items=None):
if items is None:
items = []
items.append(value)
return itemsWhat changes: Two omitted calls no longer share an object; a collection explicitly supplied by the caller is still mutated as requested.
Debugging method
Call the function twice without the argument, compare object identities and move allocation into the call path.
- Reproduce with two calls that omit the mutable argument.
- Inspect the function default through __defaults__ and compare object identities.
- Replace the mutable default with None and allocate only when the argument is None.
- Test both omitted input and an explicitly supplied empty collection.
Common wrong fix
Copying the list at the return statement still permits mutation during the call and obscures ownership. Using items = items or [] also replaces a valid empty list supplied by the caller.
Limits and edge cases
A persistent mutable default can be intentional for caching, but that state is implicit, shared and difficult to reset. Prefer an explicit cache object or a standard caching decorator so ownership and lifecycle remain visible.