Table Of Contents
Problem
Using mutable objects like lists or dictionaries as default arguments creates shared state between function calls, causing unexpected behavior.
Solution
# Wrong: Mutable default argument (creates shared state)
def bad_append(item, target_list=[]):
target_list.append(item)
return target_list
print(bad_append(1)) # [1]
print(bad_append(2)) # [1, 2] - Unexpected!
print(bad_append(3)) # [1, 2, 3] - Still growing!
# Correct: Use None and create new object inside function
def good_append(item, target_list=None):
if target_list is None:
target_list = []
target_list.append(item)
return target_list
print(good_append(1)) # [1]
print(good_append(2)) # [2] - Correct!
print(good_append(3)) # [3] - Correct!
# Dictionary example - Wrong way
def bad_add_config(key, value, config={}):
config[key] = value
return config
print(bad_add_config('name', 'John')) # {'name': 'John'}
print(bad_add_config('age', 25)) # {'name': 'John', 'age': 25} - Shared!
# Dictionary example - Correct way
def good_add_config(key, value, config=None):
if config is None:
config = {}
config[key] = value
return config
print(good_add_config('name', 'John')) # {'name': 'John'}
print(good_add_config('age', 25)) # {'age': 25} - Separate!
# Alternative pattern with copy
def append_with_copy(item, target_list=None):
if target_list is None:
target_list = []
else:
target_list = target_list.copy() # Create copy to avoid modifying original
target_list.append(item)
return target_list
original = [1, 2]
result = append_with_copy(3, original)
print(original) # [1, 2] - Unchanged
print(result) # [1, 2, 3] - New list
# Class with mutable default
class UserManager:
def __init__(self, users=None):
self.users = users if users is not None else []
def add_user(self, name):
self.users.append(name)
manager1 = UserManager()
manager1.add_user('Alice')
manager2 = UserManager()
manager2.add_user('Bob')
print(manager1.users) # ['Alice'] - Correct
print(manager2.users) # ['Bob'] - Correct
# Debugging mutable defaults
def debug_function(items=[]):
print(f"Default object ID: {id(items)}")
items.append(len(items))
return items
result1 = debug_function() # ID: 140234567890
result2 = debug_function() # ID: 140234567890 - Same object!
# Safe with immutable defaults
def safe_function(message="Hello"):
return message.upper()
print(safe_function()) # HELLO
print(safe_function("Hi")) # HI
# Factory function pattern
def create_list():
return []
def safe_append(item, target_list=None):
if target_list is None:
target_list = create_list()
target_list.append(item)
return target_list
Explanation
Python evaluates default arguments once when the function is defined, not each time it's called. Mutable objects like lists and dicts are shared across calls.
Always use None
as default and create new objects inside the function. This ensures each function call gets a fresh, independent object.
Share this article
Add Comment
No comments yet. Be the first to comment!