Mutable default arguments
The gotcha
Default argument values are evaluated once, at function definition time, and the same object is reused across calls.
Minimal repro
def append_to(item, target=[]):
target.append(item)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] ← surprise: same list reused
print(append_to(3)) # [1, 2, 3]Why it happens
When Python compiles def, it evaluates [] once and stores the resulting list object in func.__defaults__. Every call that omits target binds the parameter to that same shared object. Mutating it via append mutates the default itself.
print(append_to.__defaults__) # ([1, 2, 3],)How to avoid
Use None as the sentinel and create the mutable object inside:
def append_to(item, target=None):
if target is None:
target = []
target.append(item)
return targetFor dataclasses, use field(default_factory=list) — same problem in a different shape (see 18_dataclass_mutable_default.md).
Related
Interview angle 3
- “What does this print?” - given
def add(item, target=[])called twice, the second call returns both items. The default is evaluated once, when the function is defined, so every call without an explicit argument shares one list. - “How do you fix it?” -
target=Noneandif target is None: target = []inside the body, so a fresh list is created per call. The sentinel is the idiom precisely becauseNoneis immutable and unambiguous. - “Is it ever deliberate?” - occasionally, as a cheap memo cache:
def f(x, cache={})genuinely works because the dict persists. It is fragile - the cache is unbounded, shared, and invisible in the signature - sofunctools.lru_cacheis the version you should write.