Python's Tricky Default Arguments
Ever wonder why your Python function's default list keeps growing unexpectedly? It's a common pitfall involving mutable defaults!
Python's default arguments are evaluated once when the function is defined, not on each call.
π― System Scenario & Requirements
System Goal
Python's default arguments are evaluated once when the function is defined, not on each call.
Actors & Components
- Application or API tier
- Core service under design
- Operator or SRE observer
Functional Requirements
- Python's default arguments are evaluated once when the function is defined, not on each call.
- If a default argument is a mutable object (like a list or dictionary π), all subsequent calls that don't override that argument will share the same mutable object, leading to surprising side effects and difficult-to-debug bugs π.
- Have you ever encountered a Python function where a default list or dictionary parameter seemed to retain changes from previous calls, even though you expected it to be fresh each time?
Constraints & Assumptions
- Operational latency, consistency levels, and network partition boundaries must be enforced.
β‘ Execution Stages & Decision Trace
Stage 1 β The Unexpected Behavior
Consider a function designed to add an item to a list, with an empty list as its default: What would you expect the output of the last line to be?
Many new Pythonistas expect [4] , but the actual output reveals the shared state:
Deep Dive
Deep Dive: Stage 1 β The Unexpected Behavior
Consider a function designed to add an item to a list, with an empty list as its default:
def add_to_list(item, my_list=[]):
my_list.append(item)
return my_list
print(add_to_list(1))
print(add_to_list(2))
print(add_to_list(3, ['a', 'b']))
print(add_to_list(4))
What would you expect the output of the last line to be? Many new Pythonistas expect [4], but the actual output reveals the shared state:
[1]
[1, 2]
['a', 'b', 3]
[1, 2, 4] # Wait, what?!
Observable outcome: Many new Pythonistas expect [4] , but the actual output reveals the shared state:
Deep Dive: Stage 2 β The Fix: Using None
The standard and recommended solution is to use None as the default value, and then check inside the function body to initialize a new mutable object if None was passed. This ensures a fresh mutable object is created for each call that relies on the default.
def add_to_list_fixed(item, my_list=None):
if my_list is None:
my_list = [] # A new list is created only if no list was provided
my_list.append(item)
return my_list
print(add_to_list_fixed(1))
print(add_to_list_fixed(2))
print(add_to_list_fixed(3, ['a', 'b']))
print(add_to_list_fixed(4))
Now, the output is as expected:
[1]
[2]
['a', 'b', 3]
[4]
Observable outcome: Now, the output is as expected:
π System Outcome & Trade-offs
Observed User & Operator Behavior
User: The None idiom is your reliable pattern for ensuring that default mutable arguments behave as independent instances for each function call, preventing unexpected shared state and making your code more predictable and robust.
Operator: Always be wary of using mutable objects (lists, dictionaries, sets, custom class instances) as default arguments in Python.
Key Architectural Trade-offs
- System guarantees are traded against replication overhead and failover convergence times.
Key learnings
- Always be wary of using mutable objects (lists, dictionaries, sets, custom class instances) as default arguments in Python.
- The None idiom is your reliable pattern for ensuring that default mutable arguments behave as independent instances for each function call, preventing unexpected shared state and making your code more predictable and robust.
- Consider a function designed to add an item to a list, with an empty list as its default: What would you expect the output of the last line to be?
- The standard and recommended solution is to use None as the default value, and then check inside the function body to initialize a new mutable object if None was passed.
Apply Concepts from this Scenario
Test your understanding of the architecture decisions and trade-offs presented in this case study.
