How property Works Under the Hood
Svep för att visa menyn
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:
Tack för dina kommentarer!
Fråga AI
Fråga AI
Fråga vad du vill eller prova någon av de föreslagna frågorna för att starta vårt samtal
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:
Tack för dina kommentarer!