Python LRU cache (from functools.lru_cache) memoizes function results in a fixed-size cache, keeping the most-recently-used items and evicting least-recently-used ones when full. Caching expensive computations (recursive functions, database queries, calculations) can dramatically improve performance—but only for functions with pure (no side effects) logic and hashable arguments.
Use LRU cache for expensive pure functions; be careful caching functions with side effects or mutable arguments.
This example shows LRU caching speeding up expensive computations.
# run: python3 lru_cache.py # description: function memoization with LRU cache from functools import lru_cache import time # Expensive fibonacci without caching def fib_slow(n): if n <= 1: return n return fib_slow(n - 1) + fib_slow(n - 2) # Cached version @lru_cache(maxsize=128) def fib_fast(n): if n <= 1: return n return fib_fast(n - 1) + fib_fast(n - 2) # Compare performance print("Computing fib(30) without cache...") start = time.time() result = fib_slow(30) elapsed = time.time() - start print(f"Result: {result}, Time: {elapsed:.3f}s") print("\nComputing fib(30) with cache...") start = time.time() result = fib_fast(30) elapsed = time.time() - start print(f"Result: {result}, Time: {elapsed:.6f}s") # Cache info print(f"\nCache info: {fib_fast.cache_info()}") # Clearing cache fib_fast.cache_clear() print(f"After clear: {fib_fast.cache_info()}") # Expensive computation (database lookup simulation) @lru_cache(maxsize=32) def lookup_user(user_id): """Simulated expensive database lookup.""" print(f" (looking up user {user_id}...)") time.sleep(0.1) return f"User {user_id}" print("\n" + "="*60) print("User lookups:") print(lookup_user(1)) print(lookup_user(2)) print(lookup_user(1)) # cached, no lookup print(lookup_user(2)) # cached, no lookup print(f"\nCache hits: {lookup_user.cache_info().hits}") print(f"Cache misses: {lookup_user.cache_info().misses}") # With custom type hints (Python 3.9+) @lru_cache(maxsize=64) def expensive_computation(x: int, y: int) -> int: """Pure function suitable for caching.""" return x ** y print(f"\n{expensive_computation(2, 10)}") print(f"Cache size: {expensive_computation.cache_info().currsize}") # Disable cache (maxsize=None for unlimited, use carefully!) @lru_cache(maxsize=None) def unlimited_cache(n): """Unlimited cache (grows forever).""" return n * 2 # Disable caching @lru_cache(maxsize=0) def no_cache(n): """Cache disabled (no memoization).""" return n * 2
Basic usage:
@lru_cache(): decorate function to cache results@lru_cache(maxsize=128): max cached entries (default 128)Cache info:
func.cache_info(): returns CacheInfo(hits, misses, maxsize, currsize)func.cache_clear(): empty the cacheHashable arguments only:
Side effects and purity:
Maxsize tuning:
maxsize=None: unlimited cache (grows forever; use carefully)maxsize=0: caching disabled (decorator works but doesn't cache)Performance trade-off:
Custom caching (when LRU insufficient):
functools.cache() (Python 3.9+): unlimited, simplerfunctools.cached_property: cache class propertyThread-safety:
Typed version:
@lru_cache() caches f(1) and f(1.0) the same way@functools.lru_cache(typed=True) treats as different (Python 3.8+)Common mistakes: