Imagine you tell 10 people: “If you don’t bring your own bucket, use the one I have in my garage.”
The first person arrives, finds the bucket in your garage, and puts an apple in it. The second person arrives, finds the same bucket, and sees the first person’s apple. This is exactly how Python handles default arguments like [] or {}.
The Bug
In Python, default arguments are evaluated only once, at the moment the function is defined, not every time it is called.
def add_to_inventory(item, inventory=[]):
inventory.append(item)
return inventory
# Player 1 starts a new game
p1_items = add_to_inventory("Sword")
print(p1_items) # ['Sword']
# Player 2 starts a new game
p2_items = add_to_inventory("Shield")
print(p2_items) # ['Sword', 'Shield'] <-- WAIT, WHY DOES P2 HAVE A SWORD?
What happened?
When Python read the line def add_to_inventory(...), it created a single list in memory and attached it to the function. Every time you call the function without providing a list, it reuses that same list.
This doesn’t just happen with lists, but also with any value that changes, like timestamps. If you put datetime.now() in a function header, that “now” is frozen at the exact second you started your program.
from datetime import datetime
import time
# BAD: The time is locked at the moment the function was defined
def log_message(msg, timestamp=datetime.now()):
print(f"[{timestamp}] {msg}")
log_message("First task")
time.sleep(2)
log_message("Second task") # This will show the EXACT SAME time as the first!
The Solution
To fix this, we set the default value to None. Then, inside the function, we check if the value is None and create a fresh list if it is.
# THE CORRECT WAY
def add_to_inventory(item, inventory=None):
if inventory is None:
inventory = [] # A fresh list is created every time the function runs
inventory.append(item)
return inventory
p1_items = add_to_inventory("Sword")
print(p1_items) # ['Sword']
p2_items = add_to_inventory("Shield")
print(p2_items) # ['Shield'] - Fixed!
Why does Python do this?
You can actually see the “hidden” defaults stored inside the function object using the __defaults__ attribute.
def test_func(a=[]):
pass
print(test_func.__defaults__) # ([],)
Because test_func is an object that lives throughout the life of your program, that internal __defaults__ tuple keeps holding onto that same list object you created at the start.