@property turns methods into attribute access with validation and lazy computation
A property is a descriptor that lets a method be accessed through normal attribute syntax. Its main value is API evolution: you can start with a plain attribute, and later replace it with a computed property without breaking callers, because obj.x still works. It also centralizes validation in a setter and lets you expose a read-only attribute by defining only the getter. The trade-off is that attribute access now runs arbitrary code, which can surprise consumers if it is slow, raises, or has side effects. Properties should be cheap and side-effect-free; anything expensive should be an explicit method.
Use @property for computed values, validation, and read-only attributes.
Use @x.setter for validated assignment and @x.deleter for controlled deletion.
Use functools.cached_property for expensive computations that should be memoized per instance.
Trade-off: properties hide cost. If accessing the attribute can be slow or fail, prefer a method with a descriptive name.
Common mistake: using properties for things that mutate global state or perform IO. That violates the principle of least surprise.
Common mistake: forgetting to make the setter validate and assuming the backing attribute is protected by the property alone.
Version note: @property is ancient and stable; cached_property arrived in 3.8, and 3.8+ also improved documentation via doc on setters.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience