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

How property Works Under the Hood

Pyyhkäise näyttääksesi valikon

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?

Valitse oikea vastaus

Oliko kaikki selvää?

Miten voimme parantaa sitä?

Kiitos palautteestasi!

Osio 2. Luku 1

Kysy tekoälyä

expand

Kysy tekoälyä

ChatGPT

Kysy mitä tahansa tai kokeile jotakin ehdotetuista kysymyksistä aloittaaksesi keskustelumme

Osio 2. Luku 1
some-alt