lru_cache stores a function's returned object and returns that same object on a cache hit.
Make this comfortable
Python lru_cache: callers share the same cached mutable result
Observed contract
A cached list of receipt IDs is changed by its first caller. The second caller sees the added ID even though the loader never returned it. A separate cached tuple resists this form of mutation and gives repeatable reads.
Boundary
The list function is deliberately unsafe to expose the alias. A tuple is only shallowly immutable; nested mutable elements can still change. Cache entries also stay resident until eviction or cache_clear, so large return graphs need a memory budget.
Executable case
from functools import lru_cache
@lru_cache(maxsize=8)
def mutable_receipt_codes(region):
return ["R-47"]
@lru_cache(maxsize=8)
def receipt_codes(region):
return ("R-47",)
first_caller = mutable_receipt_codes("west")
first_caller.append("R-48")
print("shared_list", mutable_receipt_codes("west"))
print("stable_tuple", receipt_codes("west"))
print("same_cached_object", first_caller is mutable_receipt_codes("west"))Output
shared_list ['R-47', 'R-48']
stable_tuple ('R-47',)
same_cached_object TrueCost
A hit avoids recomputation, but the cache holds strong references to arguments and results up to maxsize. The wrapper is threadsafe as a data structure; simultaneous misses can still compute the same key more than once.
Common Mistakes
- Do not return a mutable cached collection directly to untrusted callers.
- A tuple containing a list is still mutable through that inner list.
- Caching is not an exactly-once execution guarantee for concurrent misses.
Connected lessons
python
lru-cache-mutable-result
