gotchapythonMajorpending
Gotcha: Python mutable default arguments persist across calls
Viewed 0 times
mutable defaultdefault argumentshared statenone sentinelfunction defaults
Error Messages
Problem
Using a mutable default argument (list, dict, set) in a function definition shares that object across all calls.
Solution
The mutable default argument trap:
You can verify this behavior:
# BUG: Default list is shared across all calls!
def add_item(item, items=[]):
items.append(item)
return items
add_item('a') # ['a']
add_item('b') # ['a', 'b'] <-- NOT ['b']!
add_item('c') # ['a', 'b', 'c'] <-- Bug!
# The default [] is created ONCE at function definition time
# Every call that uses the default shares the same list object
# FIX: Use None as sentinel
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
add_item('a') # ['a']
add_item('b') # ['b'] Correct!
# Same issue with dicts:
def update_config(key, value, config=None):
if config is None:
config = {}
config[key] = value
return config
# Same issue in class __init__:
class User:
# BAD:
def __init__(self, name, tags=[]):
self.tags = tags # All users share the same list!
# GOOD:
def __init__(self, name, tags=None):
self.tags = tags if tags is not None else []
# With dataclasses, use field(default_factory=list)
from dataclasses import dataclass, field
@dataclass
class User:
name: str
tags: list = field(default_factory=list) # Correct!You can verify this behavior:
def f(x=[]):
return x
f() is f() # True! Same object!
f.__defaults__ # ([],) -- the actual default objectWhy
Python evaluates default arguments once at function definition time, not at each call. Mutable defaults become shared state, which is almost never intended.
Context
Python function definitions
Revisions (0)
No revisions yet.