Python, renowned for its simplicity and readability, occasionally hides subtle complexities that can lead to unexpected behaviors, especially for novice programmers. One such quirk involves using mutable types like lists and dictionaries as default arguments in function definitions.
Understanding Mutable Default Arguments
Consider a seemingly innocent function:
def add_item(name, item_list=[]):
item_list.append(name)
return item_list
At first glance, this function adds a name to a list and returns it. However, the problem lies with using a list (a mutable type) as a default argument. Python’s default arguments are evaluated only once when the function is defined, not each time the function is called. This means that the item_list
is shared across all calls to add_item
that don’t provide an item list.
Demonstrating the Issue
Let’s see what happens in practice:
first_call = add_item('apple')
second_call = add_item('banana')
print("First call:", first_call)
print("Second call:", second_call)
You might expect two separate lists, but the output is surprising:
First call: ['apple']
Second call: ['apple', 'banana']
Both calls return the same list, modified by both function calls. This shared state can lead to confusing bugs, especially in larger, more complex codebases.
The Safe Alternative: None
A better approach is to use None
as a default argument and then check for it within the function:
def add_item(name, item_list=None):
item_list = item_list or []
item_list.append(name)
return item_list
Now, each call to add_item
without a specific list creates a new list:
first_call = add_item('apple')
second_call = add_item('banana')
print("First call:", first_call)
print("Second call:", second_call)
This code produces the expected result:
First call: ['apple']
Second call: ['banana']
The Case with Dictionaries
The same principle applies to dictionaries, another mutable type. Consider a function that maintains a record of scores:
def update_score(name, score, record={}):
record[name] = score
return record
Again, the record
dictionary is shared across all calls, leading to unexpected results. The remedy is the same: use None
as the default argument and assign a new dictionary within the function.
Best Practices and Conclusion
In summary, while Python’s default argument feature is handy, it requires careful consideration when dealing with mutable types. Remember:
- Use Immutable Defaults: Stick to immutable types like integers, strings, or None for default arguments.
- Create New Instances Inside: For lists or dictionaries, initialize them within the function body if the argument is
None
. - Understand Python’s Behavior: Realize that default arguments are evaluated once at the time of function definition, not each call.
By adhering to these practices, you can avoid a common pitfall that often stumps even experienced Python developers. This understanding not only prevents bugs but also leads to clearer, more predictable code.