Python Descriptors: Shallow vs Deep Resolution

This article explains the difference between shallow and deep attribute resolution when working with Python descriptors. It covers how Python's attribute lookup mechanism processes descriptor objects, contrasting direct, local namespace inspection (shallow resolution) with the full Method Resolution Order (MRO) traversal governed by __getattribute__ (deep resolution).

Understanding Python Descriptors

A descriptor is a Python object that defines at least one of the protocol methods: __get__(), __set__(), or __delete__(). When an attribute defining these methods resides on an object's class, accessing the attribute via dotted syntax (e.g., obj.attr) invokes the corresponding protocol method rather than directly reading or writing a dictionary entry.

Shallow Attribute Resolution

Shallow attribute resolution refers to querying an attribute directly from an object's local namespace without invoking the descriptor protocol or traversing the inheritance chain.

In shallow resolution:

Deep Attribute Resolution

Deep attribute resolution is the standard resolution process performed by Python's default object.__getattribute__() method whenever dotted notation (obj.attr) or getattr(obj, 'attr') is used.

Deep resolution follows a strict precedence algorithm across multiple namespaces:

  1. Data Descriptors: Python searches the class and its base classes via the MRO for a data descriptor (a descriptor defining both __get__() and either __set__() or __delete__()). If found, its __get__() method is invoked.
  2. Instance Dictionary: If no data descriptor is found, Python checks the instance namespace (obj.__dict__). If present, the stored value is returned directly.
  3. Non-Data Descriptors and Class Attributes: If the attribute is not in obj.__dict__, Python checks the class hierarchy again for non-data descriptors (defining only __get__()) or standard class variables. If a non-data descriptor is found, its __get__() method is executed.
  4. Fallback (__getattr__): If deep resolution fails to locate the attribute through the MRO and instance dictionary, Python invokes __getattr__(), if defined.

Key Differences

Feature Shallow Resolution Deep Resolution
Lookup Mechanism Direct mapping access (__dict__) __getattribute__() via dotted access (obj.attr)
Descriptor Execution Does not execute __get__(); returns descriptor object Executes __get__() and returns computed result
Hierarchy Traversal Restricted to the single queried namespace Traverses the full MRO chain
Precedence Handling None; returns the exact key in the given dictionary Prioritizes data descriptors over instance dictionaries
Primary Use Case Introspection, debugging, bypassing handlers Normal application runtime and property evaluation

Shallow resolution treats attributes as static keys within isolated dictionaries, whereas deep resolution enforces descriptor binding behavior and inheritance rules across the entire object model.