How Python Resolves Attribute Lookups
When accessing an attribute in Python using dot notation (such as
obj.attr), the interpreter executes a precise resolution
order rather than a simple dictionary query. Python navigates through
data descriptors in the class hierarchy, the instance's internal
dictionary, non-data descriptors, standard class and base class
attributes via the Method Resolution Order (MRO), and finally falls back
to dynamic fallback methods before raising an exception.
1. Data Descriptors in the Class and Base Classes
The lookup begins not at the instance level, but at the class level.
Python inspects the type of the instance (type(obj)) and
traverses its base classes using the Method Resolution Order (MRO).
During this initial pass, Python checks if the attribute name
corresponds to a data descriptor—an object defining a
__get__() method along with either __set__()
or __delete__() (such as property). If a data
descriptor is found in the class or any inherited class dictionary,
Python immediately invokes its __get__() method and returns
the result, overriding any value that might exist on the instance
itself.
2. The Instance Dictionary
(__dict__)
If no data descriptor matches the attribute name, Python shifts its
search to the instance itself. It queries the instance's internal
__dict__ namespace:
obj.__dict__['attr']If the key exists within obj.__dict__, Python returns
that value. If the class utilizes __slots__ instead of a
standard __dict__, Python checks the slot descriptor
allocated for that specific instance.
3. Non-Data Descriptors and Class Attributes
If the attribute does not exist in the instance namespace, Python
returns to the class and its base classes via the MRO to check their
respective dictionaries (cls.__dict__). At this stage, it
handles two possibilities:
- Non-Data Descriptors: If the object found defines
__get__()but lacks__set__()and__delete__()(such as standard Python functions/methods,classmethod, orstaticmethod), Python calls__get__()on the descriptor, binding the instance or class as required. - Standard Class Attributes: If the object is a regular value (such as an integer, string, or list) rather than a descriptor, Python returns it directly from the class dictionary where it was located.
4. The Fallback Mechanism
(__getattr__)
If the attribute is not found in the data descriptors, the instance dictionary, or the class hierarchy, Python searches for a custom fallback method:
- If the class defines
__getattr__(self, name), Python invokes it with the name of the missing attribute. This method allows for dynamic attribute generation or delegation. - If
__getattr__is not implemented or explicitly raises anAttributeError, Python terminates the search and raises anAttributeErrorstating that the object has no such attribute.
Overriding the Resolution Pipeline
This entire sequence is coordinated by the default implementation of
object.__getattribute__(self, name). If a class overrides
__getattribute__, it replaces this standard resolution
algorithm entirely, requiring manual invocation of
super().__getattribute__(name) to preserve default lookup
behavior.