Mutable defaults & pure updates
Debug a value that survives between calls
Lesson 19 of 20 · 13 min · Module 6: Reliable inputs & practical reports
Default argument expressions are evaluated when the function is defined. A mutable default can therefore outlive one call and surprise the next caller.
Start this lesson →Free. No installation: you write and run the code in your browser, and it is checked automatically.
By the end, you’ll be able to…
- Explain when defaults are evaluated
- Test repeated calls and input ownership
The idea
Use None as the default sentinel, and create a fresh list inside the function. A new result also protects an explicitly supplied history from unexpected edits.
def append_badly(value, history=[]):
history.append(value)
return history
# The same default list can accumulate across calls.
01Reproduce the smallest sequence
Call the starter once, then again without passing history. The second result reveals data that a single-call test could miss.
02Separate default creation from a call
The default list belongs to the function definition. Using None lets the body create a fresh list for the current call.
03Protect explicit arguments too
Changing the default alone does not prevent mutation of a list the caller passes. Return a new list to satisfy that part of the contract.
Your turn
remember_topic(topic, history=None). Return a new list containing the old history followed by topic. Calls without a history must be independent, and a supplied list must remain unchanged.What the checks look for
- Independent default calls contain only their own topic
- Extends an explicit history without editing it
- Returns a separate list for explicit empty input
Take this with you
Repeated-call tests uncover state that a single successful example cannot reveal.
Go a little further
Add a clear_history helper that returns a new empty list without altering an older reference.