Default arguments in Python functions allow parameters to have pre-defined values. However, using mutable objects (such as lists, dictionaries, or sets) as default arguments can cause unintended side effects. This is a common mistake that leads to unexpected behavior in functions.
1. What Are Default Arguments?
Default arguments are assigned values in the function definition. If the caller does not provide a value, the default is used.
def greet(name="Guest"):
print(f"Hello, {name}!")
greet() # Uses default value: "Guest"
greet("John") # Overrides default with "John"
Example with Immutable Default Argument (Safe Usage)
def increment(num=0):
num += 1
return num
print(increment()) # 1
print(increment()) # 1 (Resets each call)
Since 0
(an immutable integer) is used, it does not persist between function calls.
2. Mutable Default Arguments: The Pitfall
When a mutable object (like a list or dictionary) is used as a default argument, Python reuses the same object across multiple function calls.
Example of Mutable Default Argument (Problematic Code)
def add_item(item, items=[]): # Mutable list as default argument
items.append(item)
return items
print(add_item("Apple")) # ['Apple']
print(add_item("Banana")) # ['Apple', 'Banana']
print(add_item("Cherry")) # ['Apple', 'Banana', 'Cherry']
Why Is This Wrong?
- The default list
items=[]
is created once, not every time the function is called. - Each call modifies the same list, leading to unexpected results.
3. Correct Way: Use None
as a Default Argument
To avoid this issue, use None
as the default value and create a new list inside the function.
def add_item(item, items=None):
if items is None: # Create a new list each time
items = []
items.append(item)
return items
print(add_item("Apple")) # ['Apple']
print(add_item("Banana")) # ['Banana'] (New list created)
print(add_item("Cherry")) # ['Cherry'] (New list created)
Why Does This Work?
None
is immutable, so a new list is created in every function call.- The function behaves as expected without reusing old data.
4. Examples of Mutable Default Arguments Causing Issues
1. Default Dictionary (Incorrect Usage)
def add_entry(key, value, data={}): # Mutable dictionary
data[key] = value
return data
print(add_entry("a", 1)) # {'a': 1}
print(add_entry("b", 2)) # {'a': 1, 'b': 2} (Unexpected!)
Fix: Use None
Instead
def add_entry(key, value, data=None):
if data is None:
data = {}
data[key] = value
return data
print(add_entry("a", 1)) # {'a': 1}
print(add_entry("b", 2)) # {'b': 2} (New dictionary each time)
2. Default Set (Incorrect Usage)
def add_to_set(item, s=set()): # Mutable set
s.add(item)
return s
print(add_to_set(1)) # {1}
print(add_to_set(2)) # {1, 2} (Unexpected!)
print(add_to_set(3)) # {1, 2, 3}
Fix: Use None
Instead
def add_to_set(item, s=None):
if s is None:
s = set()
s.add(item)
return s
print(add_to_set(1)) # {1}
print(add_to_set(2)) # {2} (New set each time)
print(add_to_set(3)) # {3} (New set each time)
5. When Mutable Default Arguments Can Be Useful
Although this behavior is usually a mistake, there are specific cases where it can be useful.
Example: Caching Function Calls
If you intentionally want to maintain state between function calls, using a mutable default argument can be useful.
def cache_result(value, cache={}):
if value not in cache:
cache[value] = value * 2 # Compute and store result
return cache
print(cache_result(3)) # {3: 6}
print(cache_result(5)) # {3: 6, 5: 10}
print(cache_result(3)) # {3: 6, 5: 10} (Reuses cached value)
When to Use This Approach?
- When you want persistent data across function calls.
- When implementing memoization or caching mechanisms.
6. Summary: Key Takeaways
Mistake | Explanation | Correct Approach |
---|---|---|
Using a mutable default argument (list , dict , set ) | The same object is reused between function calls | Use None as the default and create a new object inside the function |
Modifying a mutable default argument unintentionally | Leads to data persistence across calls | Explicitly create a new instance each time |
Mutable default arguments in recursive functions | Causes incorrect behavior due to shared state | Pass mutable data explicitly |
Golden Rule:
Use None
instead of mutable objects as default arguments, unless persistence is required.