Clear an lru_cache when its source rule changes
Also published in our Blogger archive.
Clear an lru_cache when its source rule changes
By Batu
functools.lru_cache keys results from function arguments, not from mutable data the function happens to read. In this example, shipping_cost("standard") reads the rates dictionary but accepts only service as its argument. After changing rates["standard"] from 5 to 7, the cached call still returns 5 because its key is unchanged.
The rule-update boundary is therefore the right place to call the wrapper's cache_clear() method. set_rate() updates the dictionary and immediately clears every cached shipping result. A later call recomputes the standard price and returns 7. cache_info() confirms that the final call created one cache miss after the clear; the example prints only stable business results rather than implementation counters.
This pattern is appropriate when all cached outputs depend on one small, mutable rule set. cache_clear() invalidates the entire cache, not only the changed service, so it can be unnecessarily expensive for a large cache or frequent updates. In that case, include an immutable rule version in the cached function's arguments, or use a cache that supports targeted invalidation. The cache also does not synchronize a multi-step external update protocol; protect shared mutable rules with appropriate application-level concurrency control when threads can update them.
AI assistance was used to draft this article.
from functools import lru_cache
rates = {"standard": 5, "express": 12}
@lru_cache(maxsize=8)
def shipping_cost(service: str) -> int:
return rates[service]
def set_rate(service: str, amount: int) -> None:
rates[service] = amount
shipping_cost.cache_clear()
before_change = shipping_cost("standard")
rates["standard"] = 7
stale_value = shipping_cost("standard")
set_rate("standard", 7)
after_clear = shipping_cost("standard")
assert before_change == 5
assert stale_value == 5
assert after_clear == 7
assert shipping_cost.cache_info().misses == 1
print(before_change)
print(stale_value)
print(after_clear)
Expected stdout:
5
5
7