Python's Mutable Default Argument Trap: Why Your Function Remembers Too Much
You write a function, give it a list as a default argument, and everything seems fine. Then you call it a few times without passing that argument, and the results are wrong β the list is growing across calls as if it has a memory. You haven't introduced a global variable. You haven't done anything obviously wrong. But Python is quietly doing something that trips up nearly every developer the first time they see it.
This is the mutable default argument trap, and understanding it will change how you think about Python function definitions.
What you'll learn
- Why Python evaluates default arguments once, not on every call
- How to reproduce and diagnose the bug in your own code
- The canonical fix using
Noneas a sentinel value - When mutable defaults are actually intentional (and how to use them safely)
- Common variants of this trap beyond just lists
Prerequisites
This article assumes you're comfortable writing Python functions and understand basic concepts like lists, dictionaries, and function signatures. No third-party libraries are needed β everything here runs in a plain Python 3 environment.
A Simple Reproduction
The fastest way to understand the trap is to see it in action. Here's the classic example:
def append_item(item, collection=[]):
collection.append(item)
return collection
print(append_item("apple")) # ['apple']
print(append_item("banana")) # ['apple', 'banana'] β wait, what?
print(append_item("cherry")) # ['apple', 'banana', 'cherry'] β wrong
If you expected each call without a collection argument to start with an empty list, you're not alone. Most people do. But that's not what Python does.
Why This Happens: Default Arguments Are Evaluated Once
Python evaluates default argument expressions exactly once β when the def statement is executed, not when the function is called. That empty list [] is created a single time and stored as part of the function object itself.
You can see this directly by inspecting the function:
def append_item(item, collection=[]):
collection.append(item)
return collection
print(append_item.__defaults__) # ([],)
append_item("apple")
print(append_item.__defaults__) # (['apple'],)
The default value lives inside the function's __defaults__ tuple. When you mutate it inside the function body, you're mutating the same object that will be used as the default on every subsequent call. There's no reset mechanism.
This is not a bug in Python. It's a deliberate design decision consistent with Python's object model, where everything is an object and variables are references. The confusion comes from assuming that [] in a function signature means
π€ Share this article
Sign in to saveRelated Articles
Comments (0)
No comments yet. Be the first!