How property Works Under the Hood
Свайпніть щоб показати меню
property is one of the most commonly used Python built-ins. Most developers use it as a decorator without thinking about how it works. It is, in fact, a data descriptor implemented in C – and understanding it as a descriptor explains every behavior it has.
property as a Data Descriptor
property defines __get__, __set__, and __delete__, making it a data descriptor. When assigned as a class attribute, it intercepts all attribute access on instances:
12345# property is a class – you can inspect it print(type(property)) # <class 'type'> print(hasattr(property, "__get__")) # True print(hasattr(property, "__set__")) # True print(hasattr(property, "__delete__")) # True
The Decorator Syntax Is Syntactic Sugar
The @property decorator is equivalent to calling property() directly with a getter function:
123456789101112131415161718# Using @property decorator class Account: def __init__(self, balance): self._balance = balance @property def balance(self): return self._balance # Equivalent without decorator syntax class Account: def __init__(self, balance): self._balance = balance def _get_balance(self): return self._balance balance = property(_get_balance) # Same result as @property
Adding a Setter and Deleter
property returns a new property object with each chained method – fget, fset, and fdel store the three functions:
1234567891011121314151617181920212223242526272829# Full property with getter, setter, and deleter class BankAccount: def __init__(self, account_id, balance): self.account_id = account_id self._balance = balance @property def balance(self): return self._balance @balance.setter def balance(self, value): if not isinstance(value, (int, float)) or value < 0: raise ValueError(f"Balance must be non-negative, got {value!r}") self._balance = value @balance.deleter def balance(self): self._balance = 0.0 print(f"Balance for account {self.account_id} reset to 0") account = BankAccount("ACC-001", 5000.0) print(account.balance) # 5000.0 account.balance = 6500.0 # Calls setter print(account.balance) # 6500.0 del account.balance # Calls deleter print(account.balance) # 0.0
What @balance.setter Actually Does
balance.setter(func) calls property.setter() on the existing property and returns a new property object with fset set. This is why each decorated method must use the same name:
1234567891011121314151617# Inspecting what property stores class Invoice: def __init__(self, amount): self._amount = amount @property def amount(self): return self._amount @amount.setter def amount(self, value): self._amount = value print(Invoice.amount) # <property object at 0x...> print(Invoice.amount.fget) # <function Invoice.amount at 0x...> print(Invoice.amount.fset) # <function Invoice.amount at 0x...> print(Invoice.amount.fdel) # None – no deleter defined
property vs a Custom Descriptor
property is convenient for single-class use. A custom descriptor is better when the same validation logic applies to multiple attributes across multiple classes:
Дякуємо за ваш відгук!
Запитати АІ
Запитати АІ
Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат