Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lära How property Works Under the Hood | Descriptors in the Wild
Python Descriptors Explained

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:

question mark

What does @balance.setter return?

Vänligen välj det korrekta svaret

Var allt tydligt?

Hur kan vi förbättra det?

Tack för dina kommentarer!

Avsnitt 2. Kapitel 1

Fråga AI

expand

Fråga AI

ChatGPT

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:

Var allt tydligt?

Hur kan vi förbättra det?

Tack för dina kommentarer!

Avsnitt 2. Kapitel 1
some-alt