Caching Responses in Python Without External Libraries
Use Python's built-in functools.lru_cache to cache expensive function results in memory, with optional TTL expiry — no external libraries needed.
Every time a function does expensive work — a network request, a database query, a complex calculation — calling it with the same arguments wastes time. Python's built-in functools.lru_cache stores results keyed by arguments and returns the cached value on repeat calls, turning repeated computation into an instant memory lookup.
No external libraries required.
Basic Usage
from functools import lru_cache
@lru_cache(maxsize=128)
def compute_fibonacci(n):
if n < 2:
return n
return compute_fibonacci(n - 1) + compute_fibonacci(n - 2)
print(compute_fibonacci(50)) # fast after first call
maxsize=128 sets how many unique argument combinations to keep cached. When the cache is full, the least recently used entry is evicted (hence LRU). Pass maxsize=None to cache everything without a size limit.
Inspecting and Clearing the Cache
print(compute_fibonacci.cache_info())
# CacheInfo(hits=48, misses=51, maxsize=128, currsize=51)
compute_fibonacci.cache_clear()
cache_info() shows how many hits vs misses the cache has served — useful for confirming the cache is actually being used before optimizing further.
Adding a TTL (Time-to-Live)
lru_cache has no built-in expiry, but you can simulate one by adding a ttl_hash parameter whose value changes on a schedule:
from functools import lru_cache
import time
@lru_cache(maxsize=128)
def fetch_exchange_rate(currency, ttl_hash=None):
del ttl_hash # only affects the cache key, not the logic
# simulate an expensive API call
return {"USD": 1.0, "EUR": 0.92}.get(currency, 0.0)
def get_ttl_hash(seconds=3600):
"""Returns a new integer value every `seconds` seconds."""
return round(time.time() / seconds)
# Cache refreshes at most once per hour
rate = fetch_exchange_rate("EUR", ttl_hash=get_ttl_hash())
How it works: round(time.time() / 3600) returns the same integer for an entire hour, then increments. When ttl_hash changes value, lru_cache treats it as a new argument combination and re-executes the function, effectively expiring the old cached result.
Using functools.cache (Python 3.9+)
Python 3.9 added functools.cache as a simpler alias for lru_cache(maxsize=None):
from functools import cache
@cache
def expensive_lookup(key):
... # replace with your actual logic
Use this when you want unlimited caching and don't need to worry about cache size.
Limitations
- Hashable arguments only. Passing lists, dicts, or sets raises a
TypeError. Convert them to tuples or frozensets first. - In-process memory only. The cache doesn't survive process restarts and isn't shared between workers or processes.
- Not suitable for large objects. Caching large return values (dataframes, images) will increase memory usage significantly.
For distributed caches or persistent caches, use Redis with cachetools or dogpile.cache.
Conclusion
lru_cache is the right first tool for caching in Python: zero dependencies, thread-safe, and effective for CPU-bound or repeated I/O calls within a single process. Add the ttl_hash trick when you need periodic cache invalidation without pulling in a caching framework.