Python cached property: Cache Computed Attributes
python cached property: Learn how to use Python's cached_property to cache expensive attribute computations, when to apply it, and how to handle invalidation and threa...
When a Python class needs to compute an attribute that is expensive to calculate and the result does not change during the life of the instance, you can cache that value. The functools.cached_property decorator provides a clean way to do this: it computes the value once, stores it on the instance, and returns the stored value on subsequent accesses. This article explains how python cached property works, where it fits, and what limitations you need to keep in mind.
What cached_property Does
cached_property is a decorator that turns a method into a property whose value is computed only on first access. The result is stored in the instance's __dict__ under the same name as the method. Later accesses read that stored value directly, skipping the computation. This is different from a regular property, which calls the method every time the attribute is accessed.
Basic Usage
Here is a minimal example:
from functools import cached_property class Report: def __init__(self, data): self.data = data @cached_property def summary(self): # Expensive computation, e.g., aggregating data return sum(self.data) / len(self.data)
Accessing report.summary computes the average the first time. Subsequent accesses return the cached value without recomputing. The cache is per instance, so different instances compute their own values independently.
How It Differs from property and Manual Caching
A regular property recalculates on every access. Manual caching typically looks like this:
class Report: def __init__(self, data): self.data = data self._summary = None @property def summary(self): if self._summary is None: self._summary = sum(self.data) / len(self.data) return self._summary
cached_property removes the boilerplate of checking and setting a private attribute. It also avoids the need to write a setter for invalidation; you can delete the cached value directly from the instance.
When to Use cached_property
Use cached_property when:
- The computation is expensive and the result is stable for the lifetime of the instance.
- The attribute is accessed multiple times.
- The computation depends only on instance state that does not change after initialization.
If the underlying data can change, you need an explicit invalidation mechanism, which cached_property does not provide automatically.
Performance and Memory Considerations
Caching avoids repeated computation, which reduces CPU usage. The tradeoff is memory: each instance stores the computed value. For large objects or many instances, this can increase memory footprint. There is also a small lookup overhead on first access because the decorator must check whether the value exists in __dict__. This overhead is negligible compared to the cost of the computation itself.
Thread Safety and Concurrency
cached_property is not thread-safe. If multiple threads access the attribute simultaneously on the same instance, the computation may run more than once. This is acceptable when the computation is idempotent and duplicate work is tolerable. If you need to guarantee a single computation across threads, you must add your own locking around the attribute access.
Clearing and Invalidating the Cache
To invalidate the cached value, delete the attribute from the instance:
del report.summary
This removes the entry from __dict__, so the next access recomputes the value. You can also assign a new value directly, but that bypasses the computation and sets a raw value. Deleting is cleaner when you want to force a recompute.
Common Pitfalls and Limitations
cached_propertyrequires the instance to have a__dict__. Classes that use__slots__without including__dict__will raise an error.- It only works on instance methods; it cannot cache class-level or static computations.
- If the instance's
__dict__is modified elsewhere (e.g., by setting the same attribute), the cached value is overwritten. - The method name must not conflict with an existing attribute; otherwise the cache will be shadowed.
Alternatives to cached_property
If you need to cache a method that takes arguments, functools.lru_cache is a better fit. For example, a method that computes a result based on parameters can use @lru_cache on a method, but the cache is shared across all instances unless you include self in the key. For per-instance caching of a parameterized computation, you may need a manual approach.
For cases where the computation is cheap or the instance is short-lived, caching adds unnecessary complexity. A simple property is often sufficient.
When to Avoid cached_property
Avoid cached_property when:
- The attribute value can change during the instance's lifetime.
- You need thread safety without additional synchronization.
- The computation is trivial and repeated access is rare.
- You are using
__slots__without a__dict__slot.
In these situations, a regular property or a manual caching pattern with explicit invalidation is more predictable.