@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
class Temperature:
def __init__(self, celsius=0.0):
self.celsius = celsius # goes through the setter below
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError('below absolute zero')
self._celsius = float(value)
@property # read-only, derived on the fly
def fahrenheit(self):
return self._celsius * 9 / 5 + 32
@fahrenheit.setter # writable too, in the other direction
def fahrenheit(self, value):
self.celsius = (value - 32) * 5 / 9
t = Temperature(25)
print('25C in F :', t.fahrenheit) # 77.0
t.fahrenheit = 212 # set via the other unit
print('after set:', t.celsius, 'C') # 100.0 C
try:
t.celsius = -300 # validation kicks in
except ValueError as e:
print('rejected :', e)
# Output:
# 25C in F : 77.0
# after set: 100.0 C
# rejected : below absolute zeroWhen 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