The @property Decorator and Setters
15 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Most Python classes start with plain attributes such as item.price. When a rule appears later, for example "price must be positive", @property lets you add that rule while callers keep writing the same item.price. No get_price() or set_price() is needed.
Defining a Property Getter
Property is a method marked with @property that callers use like an attribute, without parentheses. Python runs the method each time the name is read.
Use @property above a method to allow it to be accessed like an attribute without parentheses.
class Circle: def __init__(self, radius): self.radius = radius
@property def area(self): return 3.14159 * (self.radius ** 2)
c = Circle(5)print(c.area) # Accessed like an attribute, not c.area()Example 1 · Computed order total
class OrderLine: def __init__(self, unit_price, qty): self.unit_price = unit_price self.qty = qty
@property def total(self): return self.unit_price * self.qty
line = OrderLine(250, 3)print(line.total)line.qty = 5print(line.total)try: line.total = 1except AttributeError: print("total is read-only")750 1250 total is read-only
What this code does
totalis not stored, so it is always in step withqty.- With no setter, assigning to it raises AttributeError.
area is a read-only computed property. If a caller tries c.area = 50, Python raises an AttributeError.
Adding a Setter with Validation
Setter is a method marked @name.setter that runs when code assigns to the property, such as item.price = 10. It is where the rules for new values are checked.
To make a property writable, define a second method with the exact same name decorated with @<property_name>.setter.
class Temperature: def __init__(self, celsius=0.0): self.celsius = celsius # Calls setter!
@property def celsius(self): return self._celsius
@celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Temperature below absolute zero is impossible!") self._celsius = float(value)Example 2 · Setter that protects a price
class Product: def __init__(self, name, price): self.name = name self.price = price
@property def price(self): return self._price
@price.setter def price(self, value): if value <= 0: raise ValueError("price must be positive") self._price = value
p = Product("Monitor", 9000)p.price = 8500print(p.price)try: p.price = 0except ValueError as e: print(e)print(p.price)8500 price must be positive 8500
What this code does
__init__assignsself.price, so even the first value passes through the setter.- A rejected value leaves the old price in place.
The setter guards the internal _celsius storage. Invalid assignments are caught immediately.
Backward Compatibility Superpower
If your class started with simple self.celsius and other code accessed it directly, converting to @property requires zero changes in client code!
- Callers continue writing
t.celsius = 25and readingt.celsius. - Validation occurs transparently behind the scenes.
- No need for unsightly
get_celsius()andset_celsius()methods.
Deleters with @<property>.deleter
You can also intercept attribute deletion using @<property_name>.deleter.
class FileRecord: @property def handle(self): return self._handle @handle.deleter def handle(self): print("Closing file handle before deletion") self._handle.close()Example 3 · Deleter runs on del
class Connection: def __init__(self, host): self._host = host
@property def host(self): return self._host
@host.deleter def host(self): print("closing", self._host) self._host = None
c = Connection("db.internal")del c.hostprint(c.host)closing db.internal None
What this code does
del c.hostcalls the deleter, which closes and clears the value.- Deleters are rare. Use them only when removing a value needs real clean-up.
Running del record.handle triggers the deleter method.
Next step
Check your understanding
12 quick questions