Python Property Decorators
In this page:
What is @property?
@property turns a method into something callers access without parentheses, like an ordinary attribute -- def full_name(self): return f'{self.first} {self.last}' becomes obj.full_name instead of obj.full_name() -- which is ideal for read-only computed values.
Example: What is @property?
class Person:
def __init__(self, first, last):
self.first = first
self.last = last
@property
def full_name(self):
return f"{self.first} {self.last}"
p = Person("Alex", "Doe")
print(p.full_name)
The Property Setter
Adding a matching @name.setter method lets that same attribute accept assignment (obj.balance = 100) while running your own validation code first, such as rejecting a negative balance before it's ever stored -- something plain attribute assignment can't intercept.
Example: The Property Setter
class Account:
def __init__(self, balance):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("Balance cannot be negative")
self._balance = value
a = Account(100)
a.balance = 200
print(a.balance)
The Property Deleter
A @name.deleter method runs custom cleanup logic when someone writes 'del obj.balance' -- useful for properties backed by external resources (like a cached file handle) that need explicit teardown rather than just vanishing.
Example: The Property Deleter
class Account:
def __init__(self, balance):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.deleter
def balance(self):
print("Cleaning up balance")
del self._balance
a = Account(100)
del a.balance
Calculated Properties
Because a property's getter is just a method under the hood, it can compute its return value fresh every time it's accessed -- a .full_name property can always reflect the current first and last name rather than going stale if those change independently.
Example: Calculated Properties
class Person:
def __init__(self, first, last):
self.first = first
self.last = last
@property
def full_name(self):
return f"{self.first} {self.last}"
p = Person("Alex", "Doe")
p.first = "Sam"
print(p.full_name)
Refactoring Legacy Code
Properties let you convert a plain public attribute into a validated one later without breaking any existing code that reads or writes obj.balance -- callers keep using the same syntax while your class quietly adds validation or computation behind the scenes.
Example: Refactoring Legacy Code
class Account:
def __init__(self, balance):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("Invalid balance")
self._balance = value
a = Account(100)
a.balance = 50 # still looks like plain attribute access
print(a.balance)
Chapter Quiz — Complete all 11 topics to unlock
0/11 topics done
Complete these topics first: