Mutable defaults: evaluated once
GoByte Skills #9: A default argument is evaluated when def runs, not on each call, and stored on the function. So a list default is one list that every call shares. Why, the None fix, and why += is the same bug in disguise.
Zoe is one of GoByte's characters. This post was drafted by AI agents in Zoe's voice, then fact checked, run and edited by the GoByte team.
A default argument is evaluated when the function is defined, not each call, so a list default is shared.
Transcript
One list is created when def runs and is stored on the function; each call that omits the argument points at that same list and appends to it, so the list keeps growing across calls.
def add(item, bag=[]):
bag.append(item)
return bag
print(add(1))
print(add(2))
print(add.__defaults__)
It prints:
[1]
[1, 2]
([1, 2],)
Why#
def is a statement, and at module level it runs once. (A def nested in another function runs on every outer call, so each call gets a fresh default.) It evaluates each default expression right then and stores the results on the function object, in add.__defaults__. Every call that omits bag binds that same stored list. append mutates it, so the second call finds the first call's leftovers. Nothing is remembered by magic: the object simply lives as long as the function does.
The fix is a sentinel. The body runs on every call, so the list is new each time:
def add(item, bag=None):
if bag is None:
bag = []
bag.append(item)
return bag
The edges#
- It is about mutation, not lists.
bag = bag + [item]builds a new list and is safe.bag += [item]mutates in place: the same bug in a disguise. - Every default is frozen at def time.
def stamp(t=time.time())returns the same timestamp forever. dataclassesrefuses the trap:items: list = []raisesValueError: mutable default <class 'list'> for field items is not allowed: use default_factory.- Ruff flags it as B006, so let a linter catch it before a reviewer does.
- The rare deliberate use is a cache shared across calls. If you mean that, say so in a comment.
Rule of thumb#
Defaults should be immutable: None, numbers, strings, tuples whose items are immutable too (a tuple holding a list shares that list). A list default is the office fridge: shared by everyone, labeled by no one, and something from last Tuesday is still in it.
Your product here? Partner with us
Back to topDiscussion
No comments yet. Signed in GoByte members with a verified e-mail can join. Community guidelines
Reading is open to everyone. Commenting and voting need a GoByte account with a verified e-mail.