Skip to content

Property Decorator

@property turns a method into something accessed like a plain attribute (no parentheses), letting you run code β€” validation, computation, logging β€” on β€œget” (and optionally β€œset” or β€œdelete”) while callers keep writing obj.value instead of obj.get_value().

Java/C#-style manual getters and setters:

class Circle:
def __init__(self, radius):
self._radius = radius
def get_radius(self):
return self._radius
def set_radius(self, value):
if value <= 0:
raise ValueError("radius must be positive")
self._radius = value
c = Circle(5)
c.set_radius(10) # verbose, and every caller must remember to use it
print(c.get_radius())
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("radius must be positive")
self._radius = value
@property
def area(self): # read-only computed property β€” no setter
return 3.14159 * self._radius ** 2
c = Circle(5)
c.radius = 10 # looks like plain attribute access, runs the validator
print(c.radius) # 10
print(c.area) # 314.159 -- computed on every access, not stored
  • Same syntax for simple and computed attributes: you can start with a plain attribute and add validation/computation later via @property without breaking every caller that wrote obj.value.
  • Read-only by default: a @property with no matching @x.setter is get-only β€” assignment raises AttributeError, a clean way to expose computed or derived values.
  • Encapsulation without ceremony: no get_x()/set_x() pairs cluttering the public API.
  • The getter, setter, and deleter must share the same method name β€” @radius.setter refers back to the radius property defined just above it.
  • Properties are computed every time they’re accessed unless you cache the result yourself β€” c.area recalculates on every read, it isn’t stored.
  • @property is implemented via the descriptor protocol (__get__/__set__ on the class) β€” the same mechanism that makes methods themselves work.
  • Assigning to a property with no setter defined raises AttributeError: can't set attribute, not a silent no-op.
class ReadOnly:
@property
def value(self):
return 42
r = ReadOnly()
r.value = 10 # AttributeError: property 'value' of 'ReadOnly' object has no setter
  1. What happens if you assign to a @property that has no @x.setter?

    Answer`AttributeError` β€” a getter-only property is read-only by default.
  2. Is a @property’s computed value cached automatically?

    AnswerNo β€” the getter method runs fresh every time the property is accessed, unless you add your own caching.
  3. What underlying mechanism makes @property work?

    AnswerThe descriptor protocol β€” `property` is a descriptor implementing `__get__`, `__set__`, and `__delete__` on the class.