Python Properties and Managed Attributes
Learn how Python properties manage attributes and when functools.cached_property is useful for reusable per-instance computed values.
Intermediate
A property lets a class present a method-backed value through ordinary attribute syntax. Reading, assigning, or deleting the attribute can run code while the public interface still looks like obj.value.
Turn a method into an attribute with @property
A property lets a class present a method-backed value through ordinary attribute syntax. Reading the property runs its getter.
|
|
The getter runs when circle.diameter is read.
A property can calculate a value
A property does not have to store its result. A getter can calculate a value from other instance state while callers still use attribute syntax.
|
|
area behaves like a read-only attribute even though its value is computed by a method.
Make a property writable with a setter
A getter alone does not define how assignment should work. Add @name.setter when assignments need validation, conversion, or another controlled behavior.
|
|
The setter validates an assignment before updating the backing attribute.
Use a backing attribute
A common pattern is to expose a public property name and keep the stored value under another instance attribute such as _name. The underscore communicates that the storage detail is internal.
|
|
name is the public property; _name is the instance attribute used for storage.
Add deletion with a property deleter
A property can define a deleter with @name.deleter. Then del obj.name invokes that function, which decides what deletion means for the managed state.
|
|
The deleter changes the managed state when del session.token is executed.
A setter and deleter keep the same property name
The getter, setter, and deleter all use the same property name. The decorator form attaches the corresponding accessor to the property.
|
|
All three access paths—read, assignment, and deletion—share the same property name.
Properties preserve a simple public interface
A property is useful when a value should look like ordinary attribute data to callers but needs computed behavior, validation, conversion, or controlled deletion behind the scenes.
|
|
Callers use account.balance consistently even though reads and writes execute methods.
Property objects and the descriptor connection
A property object implements the descriptor behavior that lets Python manage attribute access. You do not need to implement the descriptor protocol to use properties; @property provides the ready-made mechanism.
|
|
The class stores a property object under username; accessing profile.username invokes that property’s getter.
Choose a property when attribute syntax communicates the right abstraction
Use a property when callers should think of the result as an attribute. Use a normal method when the operation is naturally an action, takes meaningful arguments, or is better expressed as a call.
|
|
An argument-free derived value reads naturally as a property; an operation that accepts a rate is clearer as a method.
Cache an expensive computed attribute per instance
For a computed value that is expensive to produce and reused often,
functools.cached_property turns a method into an attribute whose value is calculated on the first lookup and stored on that instance. Later lookups read the stored value rather than calling the method again. Unlike an ordinary
@property getter, it does not automatically recalculate on every access.
|
|
The first report calculates its total once, reuses it on the next access, and the second report computes an independent value.
The stored value belongs to one instance; it is not a shared cache keyed by function arguments. This is useful when a result depends on stable instance data, such as parsed metadata or a summary of an immutable collection.
Refresh or replace the cached value
The cached result can become stale if the data it depends on changes. Delete the instance attribute with
del obj.name to let the descriptor calculate it again. Assignment to that same name is also allowed and takes precedence over the descriptor, so a cached property can be temporarily replaced with an instance value.
|
|
After the list changes, the cached count stays at 1. Assignment overrides it with 99; deleting that attribute makes the next read recompute the count as 2.
There are practical limits:
@cached_property needs an instance with a mutable
__dict__, so it does not work on a slot-only class that omits
__dict__. Since Python 3.12 it also has no internal lock to guarantee the getter runs only once during concurrent first access. Use it when an occasional repeated calculation is safe, or provide your own synchronization if needed.
Quick check
The cached value has already been read once. What does the final expression print?
from functools import cached_property
class Sample:
def __init__(self):
self.calls = 0
@cached_property
def value(self):
self.calls += 1
return self.calls + 5
sample = Sample()
sample.value
print()
Quick check
After the input list changes, which expression makes the cached count calculate again?
from functools import cached_property
class Data:
def __init__(self):
self.values = [1]
self.calls = 0
@cached_property
def count(self):
self.calls += 1
return len(self.values)
data = Data()
data.count
data.values.append(2)
del data.count
print()
Key takeaways
- Use @property to expose a method through attribute-reading syntax.
- Use @name.setter to control assignments and @name.deleter to control deletion.
- A backing attribute such as _value can store the state managed by a property.
- Properties provide a managed-attribute interface without requiring callers to change from attribute syntax to method calls.
- Use a normal method when the operation is better understood as an action or needs meaningful arguments.
- cached_property computes a value on the first lookup and stores it on that instance for reuse.
- Delete a cached attribute to recalculate after its inputs change; assignment can override the cached value.
- cached_property needs a mutable instance __dict__; its getter is not guaranteed to run only once under concurrent first access.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.