Misuse of mutable default arguments

Loading

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

MistakeExplanationCorrect Approach
Using a mutable default argument (list, dict, set)The same object is reused between function callsUse None as the default and create a new object inside the function
Modifying a mutable default argument unintentionallyLeads to data persistence across callsExplicitly create a new instance each time
Mutable default arguments in recursive functionsCauses incorrect behavior due to shared statePass mutable data explicitly

Golden Rule:

Use None instead of mutable objects as default arguments, unless persistence is required.

Leave a Reply

Your email address will not be published. Required fields are marked *