cached_property computes an attribute on first access and stores the result in the instance dictionary.
Python cached_property: delete the instance value when source state changes
Operation contract
The invoice stores an immutable tuple of minor-unit amounts and counts summary evaluations. Two reads use one computed total. Replacing the tuple without deleting the cached attribute would leave a stale total; the setter therefore deletes the cached value after validating the new amounts. A later read recomputes it. The cache is per instance, not a global memo table.
Failure and ownership boundary
This descriptor needs a mutable instance __dict__. It is unsuitable for a slots-only instance without one, and concurrent first reads may run the getter more than once. Invalidation belongs with every mutation path. Python properties: validate mutation at the public attribute boundary, Python lru_cache: bound retention and include the revision in the key and Python dataclasses: frozen fields require an immutable value model show alternatives.
Working program
from functools import cached_property
class Invoice:
def __init__(self, amounts):
self.computations = 0
self.replace(amounts)
def replace(self, amounts):
candidate = tuple(amounts)
if len(candidate) > 20 or any(type(amount) is not int or not 0 <= amount <= 1000 for amount in candidate):
raise ValueError("bounded minor units")
self.amounts = candidate
self.__dict__.pop("total", None)
@cached_property
def total(self):
self.computations += 1
return sum(self.amounts)
invoice = Invoice([125, 75])
print(invoice.total, invoice.total, invoice.computations)
invoice.replace([125, 100])
print(invoice.total, invoice.computations)Output
200 200 1
225 2Costs and limits
An uncached total scans n amounts; a later cached read is an attribute lookup. The cached integer and instance dictionary entry remain until deletion or collection. This program has no concurrent callers, so it does not establish one-computation semantics under a race.
Common Mistakes
- Changing source state without invalidation leaves a stale cached value.
- cached_property requires an instance dictionary and does not serialize concurrent first access.
Connected lessons
Python properties: validate mutation at the public attribute boundary, Python lru_cache: bound retention and include the revision in the key, Python dataclasses: frozen fields require an immutable value model.
