We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Stdlib Mastery step 25 of 55
cached_property: compute once, release with the instance, invalidate on demand
Build a class whose expensive derived value is computed at most once per instance, released when the instance dies, and explicitly invalidatable.
class Report:
def __init__(self, rows: list[int]) -> None: ...
rows: list[int]
computations: int
total: int # the derived value: sum(self.rows)
def invalidate(self) -> None: ...
-
totalissum(self.rows). Reading it repeatedly must incrementcomputationsonce. -
invalidate()discards the cached value so the next read recomputes. Calling it on an instance that has never computedtotalmust not raise. -
Report.__init__should copy the incoming rows (list(rows)).
def solve(rows: list[int], script: list[str]) -> dict[str, object]:
solve builds a Report(rows) and replays a script of operations:
| op | effect |
|---|---|
"read" |
append report.total to the reads list |
"invalidate" |
call report.invalidate() |
"add" |
report.rows.append(5) |
Anything else raises ValueError. Afterwards it records computations, takes
a weakref.ref to the report, dels it, calls gc.collect(), and returns
{"reads": [...], "computations": int, "leaked": bool} where "leaked" is
tracker() is not None and must be False.
Why the leak test is here. The obvious hand-rolled alternative is a
class-level dict keyed on the instance. It caches correctly, it invalidates
correctly, and it keeps every Report alive for the life of the process.
cached_property stores the value in the instance’s own __dict__, so
the cache is part of the instance and dies with it.
Two things about cached_property you should know while writing this.
The per-property lock was removed in 3.12 — it serialised every thread touching any instance of the class, for a guarantee most callers did not need. Concurrent computation is now possible and last-writer-wins. Code written before 3.12 that relied on the implicit once-only guarantee under threading is now racy, with no deprecation warning.
A class with __slots__ and a cached_property type-checks clean under
--strict and raises TypeError: No '__dict__' attribute ... to cache on
first access. It is the best one-line demonstration in the course that
--strict is necessary and not sufficient.
There is no invalidate() in the stdlib. del obj.total works but raises
AttributeError when nothing was computed, so a general invalidator needs the
pop form — reaching into __dict__ here is the supported mechanism, not a
violation, because that is the documented storage location.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.