The mutable default argument trap
A def's default argument value is evaluated once, at function-definition time — not once per call.
If that default is a mutable object (list, dict, set), every call that doesn't override it shares the
same object, so mutations leak across calls. This is one of the most common real bugs in beginner and even
intermediate Python code, and it's also why idiomatic code uses None as the sentinel default instead.
def append_item(item, bucket=[]):
bucket.append(item)
return bucket
print(append_item(1)) # [1]
print(append_item(2)) # [1, 2] <- same list object as before!
print(append_item(3, bucket=[])) # [3] <- explicit fresh list breaks the sharing
print(append_item(4)) # [1, 2, 4] <- back to sharing the original default
The correct idiom: default to None, create the mutable object inside the function body.
def append_item_fixed(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucket
print(append_item_fixed(1))
print(append_item_fixed(2)) # independent list this time