@property — Computed Attributes
Expose methods as attributes, add validation, and keep a clean public API without breaking callers.
Coming from other languages you might reach for get_price() and set_price(). Python's answer is the @property decorator: it lets a method be accessed like a plain attribute, so callers write obj.price while you keep full control behind the scenes.
The pattern
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self): # accessed as circle.area, no ()
return 3.14159 * self.radius ** 2
c = Circle(10)
print(c.area) # 314.159 — looks like an attribute, runs like a methodValidation with a setter
Add a matching @name.setter to guard assignment. This is the key benefit: you can start with a plain attribute, and later wrap it in a property to add validation without changing a single line of calling code.
Read-only by design
A property with a getter but no setter is read-only — assigning to it raises AttributeError. That is exactly how you expose a derived value that should never be set directly.
Example
When to use it
- A User model exposes 'password' as a write-only property that automatically hashes on assignment.
- A lazy-loading class computes a 'data' property on first access and caches the result for subsequent calls.
- A geometric class exposes 'diameter' and 'radius' as paired properties that stay in sync.
More examples
Getter and setter with validation
Uses a setter to validate that a percentage stays within 0-100 before storing it.
class Percentage:
@property
def value(self): return self._value
@value.setter
def value(self, v):
if not 0 <= v <= 100:
raise ValueError(f'{v} is not 0-100')
self._value = v
p = Percentage()
p.value = 75
print(p.value) # 75Deleter method
Implements a @data.deleter to allow 'del obj.data' to clear a cached value.
class CachedData:
def __init__(self): self._cache = None
@property
def data(self):
if self._cache is None:
self._cache = 'expensive result'
return self._cache
@data.deleter
def data(self):
self._cache = None
print('Cache cleared')
cd = CachedData()
print(cd.data) # expensive result
del cd.data # Cache clearedPaired computed properties
Exposes both radius and diameter as properties that derive from the same internal value.
class Circle:
def __init__(self, radius): self._r = radius
@property
def radius(self): return self._r
@property
def diameter(self): return self._r * 2
@diameter.setter
def diameter(self, d): self._r = d / 2
c = Circle(5)
print(c.diameter) # 10
c.diameter = 14
print(c.radius) # 7.0
Discussion