History of the question
In Python’s data model, attribute access follows a strict protocol where __getattribute__ is defined on the base object class and serves as the primary interceptor for every attribute lookup. This method is invoked unconditionally for all attribute accesses, existing or not, making it the first line of defense in the resolution chain. In contrast, __getattr__ is an optional hook that the interpreter calls only when the normal search through the instance dictionary and class hierarchy fails to locate the requested name.
The problem
When a subclass overrides __getattribute__ to customize behavior such as logging or access control, any direct attribute access within the method body—such as self.attr or self.__dict__—triggers the same overridden method recursively. This creates an infinite loop because the lookup mechanism has been hijacked without a base case to terminate the recursion, eventually exhausting the call stack and raising a RecursionError.
The solution
To safely implement __getattribute__, you must delegate to the base implementation using super().__getattribute__(name) or object.__getattribute__(self, name). This bypasses the overridden logic and performs the actual attribute retrieval from the instance dictionary or class hierarchy without re-entering the custom method. The pattern ensures you can wrap, validate, or transform the result while maintaining the integrity of the object model and preventing infinite loops.
Code example
class SafeProxy: def __init__(self, wrapped): # Must use super() here to avoid recursion during initialization super().__setattr__('_wrapped', wrapped) def __getattribute__(self, name): # Log the access before retrieval print(f"Accessing: {name}") # Delegate to object to avoid infinite recursion return super().__getattribute__(name)
Scenario
A development team needs to implement an audit trail for a legacy ORM model where every field access must be logged for compliance reasons without modifying the original model classes. They require a solution that intercepts reads transparently to avoid breaking existing business logic across hundreds of modules.
Problem description
The system requires intercepting both existing and missing attributes to record timestamps and user actions. Simply subclassing and adding logging to individual methods is infeasible due to the large number of dynamic fields. The solution must be transparent to existing code and cannot alter the public interface of the models.
Solution 1: Monkey-patching the model methods
This approach involves dynamically replacing methods on the class at runtime to inject logging calls, targeting specific behaviors without altering source definitions. It allows conditional application based on configuration and avoids inheritance complications. However, it fails to intercept direct attribute access to data descriptors or simple values, requires maintenance for every new method, and breaks when internal implementation details change.
Solution 2: Using __getattr__ for logging
Implementing __getattr__ to log access to missing attributes only provides a simple fallback mechanism. It is safe from recursion issues and easy to implement with minimal boilerplate. Unfortunately, it only triggers for attributes not found in the instance or class, missing the majority of accesses to existing fields, which defeats the audit requirement for comprehensive logging.
Solution 3: Proxy class with __getattribute__
Creating a wrapper class that implements __getattribute__ intercepts all attribute reads before delegating to the wrapped ORM instance, capturing every access uniformly. This maintains transparency via composition and allows pre- and post-processing without touching the legacy code. The trade-off is the requirement for careful recursion handling and a slight performance overhead due to the additional method call on every attribute access.
Chosen solution
The team selected the proxy approach with __getattribute__ because compliance regulations mandated capturing every attribute read, including simple data fields that methods never touch. The proxy pattern provided complete interception capabilities while maintaining encapsulation, allowing the legacy ORM to remain pristine and unaware of the auditing layer. This choice sacrificed minimal performance for comprehensive coverage and audit integrity.
Result
The implementation successfully logged over 50,000 attribute accesses per hour in production without a single recursion error or modification to the legacy code base. The delegation pattern using super() ensured stable operation, and the proxy could be disabled in testing environments by simply removing the wrapper instantiation, demonstrating the flexibility of the approach.
Why does accessing self.__dict__ inside __getattribute__ trigger infinite recursion?
When you write self.__dict__ inside an overridden __getattribute__ method, Python must look up the attribute named __dict__ on the instance. This lookup invokes your custom __getattribute__ method again, which tries to access self.__dict__ again, creating an endless cycle. To break this loop, you must use object.__getattribute__(self, '__dict__'), which bypasses your override and retrieves the dictionary directly from the base object implementation.
How does __getattribute__ affect descriptor protocols differently than __getattr__?
__getattribute__ sits at the very beginning of the attribute resolution chain, meaning it intercepts lookups before the descriptor protocol checks for __get__ methods. If your implementation returns a value without delegating to super(), descriptors like property or custom data descriptors are completely bypassed. In contrast, __getattr__ only executes after the descriptor protocol and instance dictionary lookup have both failed, so it never intercepts descriptors that exist in the class hierarchy.
What is the consequence of raising AttributeError manually inside __getattribute__?
Unlike standard attribute access where an AttributeError might trigger __getattr__ as a fallback, Python treats __getattribute__ as the authoritative source. If your custom implementation raises AttributeError, the interpreter propagates the exception immediately without attempting to call __getattr__. This means you cannot rely on __getattr__ to handle missing attributes if your primary hook fails; instead, you must handle missing keys within __getattribute__ or ensure you delegate to the parent implementation which raises the exception correctly.