Memoize an expensive pure lookup with cache
@functools.cache memoizes a function result by its arguments. In this recipe, price_in_cents() receives a SKU string and returns the matching integer price from an immutable catalog. The intentionally long generated prefix makes finding the named entries a linear lookup; the fixture remains local and deterministic. Calling the function for "paper" twice produces 1250 both times, while the body runs once for that argument. A different argument, "ruler", creates the second function evaluation.
The evaluations counter is outside the cached result and makes the cache behavior visible. It is not part of the lookup's answer: given an unchanged CATALOG and the same SKU, price_in_cents() is pure. cache is a thin unbounded cache, so it fits a bounded key space or a process with explicit invalidation. price_in_cents.cache_clear() is available when a replacement catalog must take effect.
Arguments must be hashable; a list or dictionary cannot be a cache key. Do not decorate lookups whose answer can change with time, external state, or a modified catalog unless their cache lifetime is controlled. Concurrent first calls can also compute the same missing key more than once, even though the cache structure remains coherent.
This Batu Lab Notes article was drafted with AI assistance.
from functools import cache
CATALOG = tuple((f"sku-{number:05d}", number) for number in range(20_000)) + (
("paper", 1250),
("pencil", 90),
("ruler", 275),
)
evaluations = 0
@cache
def price_in_cents(sku):
global evaluations
evaluations += 1
for known_sku, cents in CATALOG:
if known_sku == sku:
return cents
raise KeyError(sku)
first = price_in_cents("paper")
second = price_in_cents("paper")
third = price_in_cents("ruler")
assert (first, second, third) == (1250, 1250, 275)
assert evaluations == 2
print(f"prices: {first} {second} {third}")
print(f"function evaluations: {evaluations}")
Expected stdout:
prices: 1250 1250 275
function evaluations: 2
Comments
Post a Comment