The Attribute Lookup Chain
Свайпніть щоб показати меню
Before understanding descriptors, you need to understand how Python finds an attribute when you write obj.name. The answer is not as simple as "look it up in the object's dictionary." Python follows a specific chain of lookups – and descriptors are what make that chain powerful.
The Lookup Order
When you access obj.attr, Python follows this sequence:
- the type's MRO (method resolution order) is searched first for a data descriptor on the class or its parents;
- if no data descriptor is found, the instance's
__dict__is checked; - if not found there, the type's MRO is searched again for a non-data descriptor or plain class attribute;
- if none of the above match,
AttributeErroris raised.
1234567891011121314# Observing the basic lookup chain class Product: category = "electronics" # Class attribute def __init__(self, name, price): self.name = name # Instance attribute stored in __dict__ self.price = price laptop = Product("ThinkPad", 1200.0) print(laptop.name) # Found in instance __dict__ print(laptop.category) # Not in __dict__ – found on the class print(laptop.__dict__) # {'name': 'ThinkPad', 'price': 1200.0} print(Product.__dict__.keys()) # Contains 'category', '__init__', etc.
__dict__ at the Instance and Class Level
Every regular instance has a __dict__ that stores its attributes. The class also has a __dict__ that stores methods and class-level attributes:
1234567891011121314151617# Comparing instance and class __dict__ class Invoice: tax_rate = 0.2 # Class attribute – stored in Invoice.__dict__ def __init__(self, invoice_id, amount): self.invoice_id = invoice_id # Instance attribute self.amount = amount invoice = Invoice("INV-001", 5000.0) print(invoice.__dict__) # {'invoice_id': 'INV-001', 'amount': 5000.0} print(Invoice.__dict__["tax_rate"]) # 0.2 # Instance attribute shadows class attribute with the same name invoice.tax_rate = 0.15 # Creates a new entry in instance __dict__ print(invoice.tax_rate) # 0.15 – instance __dict__ found first print(Invoice.tax_rate) # 0.2 – class attribute unchanged
How type.__getattribute__ Drives the Lookup
The entire lookup process is implemented in type.__getattribute__. You rarely call it directly, but understanding that it exists explains why descriptors work – they are objects the __getattribute__ machinery knows how to invoke:
123456789101112# Explicit lookup through __getattribute__ class Config: debug = False def __init__(self, env): self.env = env config = Config("production") # These two lines do the same thing print(config.env) print(object.__getattribute__(config, "env"))
When Instance Attributes Shadow Class Attributes
Instance attributes always shadow class attributes of the same name – unless the class attribute is a data descriptor (covered in Chapter 3). This distinction is the key to understanding why descriptors exist:
123456789101112# Instance attribute shadowing a class attribute class Report: status = "pending" report = Report() print(report.status) # "pending" – from class report.status = "approved" # Written to instance __dict__ print(report.status) # "approved" – instance shadows class del report.status # Removing the instance attribute print(report.status) # "pending" – class attribute visible again
Дякуємо за ваш відгук!
Запитати АІ
Запитати АІ
Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат