A Practical Guide to functools.lru_cache in Python
If you’ve ever written a function that gets called repeatedly with the same arguments, you know the pain of recomputing the same result over and over. The functools.lru_cache decorator is the antidote to this inefficiency.
What is lru_cache?
lru_cache stands for Least Recently Used cache. It’s a decorator that stores the results of a function in memory, so subsequent calls with the same arguments return the cached result instead of re-executing the function. This technique is called memoization.
Here’s the simplest possible example:
from functools import lru_cache
@lru_cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# First call - computes and caches
print(fibonacci(30)) # Takes a moment
# Subsequent calls - instant
print(fibonacci(30)) # Returns cached result
Without caching, computing fibonacci(30) would make over 2 million recursive calls. With lru_cache, it makes exactly 31 calls. The difference is night and day.
The maxsize Parameter
By default, lru_cache stores an unlimited number of results. In production, you almost always want to set a limit:
@lru_cache(maxsize=128)
def expensive_operation(x):
# Some costly computation
return x ** 2
When the cache is full and a new entry is added, the least recently used entry gets evicted. Setting maxsize=None disables the size limit (use with caution), while maxsize=0 effectively disables caching entirely.
A power of two (128, 256, 512) is a common choice, but you should size it based on your function’s input space. If you’re caching results for a function that accepts any integer, a small cache might cause thrashing. If the domain is small (like enum values), a small maxsize is fine.
Inspecting the Cache
Sometimes you need to debug or monitor your cache. lru_cache provides two methods for this:
@lru_cache(maxsize=10)
def square(x):
return x * x
square(5)
square(5)
square(10)
square(3)
print(square.cache_info())
# CacheInfo(hits=1, misses=3, maxsize=10, currsize=3)
The output shows:
- hits: How many times a cached result was returned
- misses: How many times the function actually ran
- maxsize: The maximum cache size
- currsize: Current number of cached entries
This is invaluable for tuning your cache and spotting when things aren’t working as expected.
Clearing the Cache
There are two ways to clear a cached function’s results:
# Clear just this function's cache
square.cache_clear()
# Or invalidate specific arguments (Python 3.9+)
square.cache_partial_clear(5) # Only removes the entry for argument 5
cache_clear() is useful when you know the underlying data has changed and you need a fresh start.
When NOT to Use lru_cache
This isn’t a silver bullet. Avoid lru_cache when:
- Your function has side effects (it modifies state, writes to files, makes network calls)
- Your arguments are mutable (lists, dicts, custom objects)
- Your function returns different results for the same inputs (not deterministic)
Also, remember that cached arguments must be hashable. If you’re passing unhashable types, you’ll get a TypeError.
A Real-World Example
Here’s something more practical than fibonacci - fetching data from an API with caching:
from functools import lru_cache
import requests
@lru_cache(maxsize=100)
def get_user(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()
# First call hits the API
user = get_user(42)
# This one returns instantly from cache
user_cached = get_user(42)
Of course, you’d want to handle errors and set cache expiration for real APIs, but this shows the pattern nicely.
Comparison with Other Caching Methods
While lru_cache is the go-to solution for simple in-memory caching, Python offers other options depending on your needs:
- cachetools: Provides additional cache implementations like TTLCache (time-based expiration) and LFU (least frequently used)
- diskcache: Caches to disk instead of memory, useful for large datasets
- functools.cache: Equivalent to
@lru_cache(maxsize=None), simpler if you don’t need size limits
For most web applications, consider combining lru_cache with HTTP caching headers or a Redis-backed solution for distributed systems.
See Also
- The functools Module - Other useful tools in Python’s functools
- Python Decorators Explained - Understanding how decorators work under the hood