Python evaluates a default argument when the function is defined, so a mutable default can be shared across later calls.
Python function arguments: avoid shared mutable defaults
Operation contract
The receipt-tag function defaults to None and allocates a fresh list for that call. When a list is supplied, it copies it before appending so the caller retains its own collection. Those are two ownership decisions, not one syntax fix. The output proves both independent defaults and preservation of a supplied tag list.
Failure and ownership boundary
Using an empty list as the default can retain earlier callers’ tags. Using tags or [] would also treat a supplied empty list as though no object were supplied, which may be wrong when preserving identity is intentional. Python variables: names refer to objects, assignment does not copy explain why default allocation and caller-owned mutation need separate rules.
Working program
def receipt_tags(receipt_id, tags=None):
owned_tags = [] if tags is None else list(tags)
owned_tags.append(receipt_id)
return owned_tags
shared_tags = ["reviewed"]
print(receipt_tags("R41"))
print(receipt_tags("R42"))
print(receipt_tags("R43", shared_tags))
print(shared_tags)Output
['R41']
['R42']
['reviewed', 'R43']
['reviewed']Costs and limits
Copying t supplied tags takes O(t) time and storage; the default empty allocation is bounded. A shallow copy still shares mutable tag objects.
Common Mistakes
- Do not put a mutable list in a reusable default.
- Define whether the callee mutates or copies supplied state.
Connected lessons
Python variables: names refer to objects, assignment does not copy, Python functions: define units, validate input and return a result, Python closures: capture loop values at the intended time.
Apply this boundary
Python scope: local assignment, nonlocal state and late binding, Python unpacking: keyword collisions and strict record pairing.
Follow the related contract
Python input and output: separate parsing from terminal I/O.
Follow the service contract
Python positional-only and keyword-only parameters: keep call roles explicit.
