Misused caches leak memory, return stale or shared mutable data, and miss more often than expected.
Choosing maxsize: the default is 128. An unbounded cache (maxsize=None or @cache) is fastest but grows forever, which is a leak in long-running services with unbounded argument spaces. Check cache_info() in production to see hit rates and sizes.
Cache keys are built from positional and keyword arguments as passed. Equal values of different types, like 2 and 2.0, usually share an entry (they compare equal and hash alike) unless typed=True; the docs say "usually" because a single int or str argument takes a fast path with its own key. f(a=1, b=2) and f(b=2, a=1) are different keys.
Methods: decorating a method with @lru_cache caches on the function object at class level, with self as part of the key. The cache holds a strong reference to every instance it has seen, so instances are never garbage-collected while their entries remain (linters flag this as B019). Options: cached_property for zero-argument methods, a per-instance cache created in __init__ (self.lookup = lru_cache()(self._lookup)), or a module-level function keyed by an ID.
Unhashable arguments: convert lists to tuples and dicts to frozenset(d.items()) in a thin wrapper before calling the cached function.
Time-based expiry isn't built in. Include a coarse time bucket in the key (int(time.time() // 300)), or use a library such as cachetools for TTL caches.
Example
import gc
import weakref
from functools import lru_cache
@lru_cache(maxsize=None)
def scale(x, factor):
return x * factor
scale(2, 3); scale(2.0, 3)
print(scale.cache_info().currsize) # 1 -- 2 == 2.0 share a key
typed = lru_cache(maxsize=None, typed=True)(scale.__wrapped__)
typed(2, 3); typed(2.0, 3)
print(typed.cache_info().currsize) # 2
class Repo:
@lru_cache(maxsize=None) # caches on the class, keyed by self
def lookup(self, key):
return key.upper()
r = Repo(); r.lookup("a")
alive = weakref.ref(r)
del r; gc.collect()
print(alive() is not None) # True: the cache keeps r alive
Repo.lookup.cache_clear(); gc.collect()
print(alive() is None) # TrueRemember
- Bound the cache in long-running processes; watch
cache_info() typed=Trueseparates 1 and 1.0; keyword order matters for keys@lru_cacheon methods keeps instances alive; prefer per-instance caches
Watch out for
- Decorating methods of short-lived objects with
@lru_cache, causing a memory leak.