Attribute lookup, step by step
advanced22 min readLesson 107 of 169
Trace the four-stage lookup order and exploit it deliberately.
Everything is a namespace lookup
You already know obj.x reads an attribute. Advanced Python means knowing the
exact order Python consults when resolving obj.x, and using that order as
a design tool.
For obj.x on an instance, Python (conceptually) tries, in order:
- Data descriptor on the type (a descriptor defining
__set__or__delete__) โ e.g.property, most validators. - Instance dictionary โ
obj.__dict__. - Non-data descriptor on the type (defines only
__get__) โ e.g. plain functions (that is how bound methods are created!),staticmethod,classmethod,functools.cached_propertybefore first call. __getattr__fallback (if defined) โ called only when normal lookup fails. Great for delegation, dangerous for hiding typos.
Two consequences worth internalizing:
- A data descriptor beats an instance
__dict__entry. That is whypropertysetters can validate even though you could assignobj.x = 5. - Functions are non-data descriptors; the "bound method" you call is produced
at lookup time by
__get__. Nothing magic is stored on the instance.
class Loud:
def __getattr__(self, name): # only called on MISS
return f"<fallback for {name}>"
loud = Loud()
loud.real = 1
print(loud.real) # 1 (found normally)
print(loud.anything) # <fallback for anything>
Use vars(obj) / obj.__dict__ to inspect the instance side, and
type(obj).__mro__ to see the class side. When behavior surprises you, ask:
"which step of the lookup produced this?"