Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Python lru_cache: callers share the same cached mutable result

Last updated: 1 Oct 20265 min read
tutorial
IntermediateBy AITrove Editorial

lru_cache stores a function's returned object and returns that same object on a cache hit.

Download Python source kit

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

python
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

Output
shared_list ['R-47', 'R-48']
stable_tuple ('R-47',)
same_cached_object True

Cost

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

Test this contract.

python
lru-cache-mutable-result
Storage details