Mutable default argument keeps old values
A function with a default list or dict remembers items between calls. The default is created once, when the function is defined — not on every call.
What it means
def f(items=[]) evaluates [] exactly once and reuses that same list object every time the default is needed. Appending to it mutates the shared default, so the second call sees what the first call added. No error is raised, which is what makes it so hard to spot: the function is simply wrong on its second call.
Common causes
1. A default list that gets appended to
Each call without an argument shares — and grows — the same list.
Breaks
def add_tag(tag, tags=[]):
tags.append(tag)
return tags
add_tag("a") # ['a']
add_tag("b") # ['a', 'b'] <- surpriseWorks
def add_tag(tag, tags=None):
if tags is None:
tags = []
tags.append(tag)
return tags2. A default dict used as a cache by accident
Same mechanism with a dict: data from one call leaks into the next.
Breaks
def count(words, seen={}):
for w in words:
seen[w] = seen.get(w, 0) + 1
return seenWorks
def count(words, seen=None):
seen = {} if seen is None else seen
for w in words:
seen[w] = seen.get(w, 0) + 1
return seen3. A list default in a dataclass
dataclasses catch this one and raise ValueError: mutable default <class 'list'> for field ... is not allowed: use default_factory.
Breaks
@dataclass
class Order:
items: list = []Works
from dataclasses import dataclass, field
@dataclass
class Order:
items: list = field(default_factory=list)How to find it in your own code
Use None as the default for anything mutable and create the fresh object inside the function. Linters flag this (flake8-bugbear's B006, pylint's dangerous-default-value) — turning that rule on stops it ever reaching production.
Still not sure why yours breaks?
Paste it into the visualizer and watch it run line by line, with every variable at every step. Free, and it runs in your browser.