Deep Analysis and Comparison of __getattr__ vs __getattribute__ in Python

Nov 27, 2025 · Programming · 9 views · 7.8

Keywords: Python | __getattr__ | __getattribute__

Abstract: This article provides an in-depth exploration of the differences and application scenarios between Python's __getattr__ and __getattribute__ special methods. Through detailed analysis of invocation timing, implementation mechanisms, and common pitfalls, combined with concrete code examples, it clarifies that __getattr__ is called only as a fallback when attributes are not found, while __getattribute__ intercepts all attribute accesses. The article also discusses how to avoid infinite recursion, the impact of new-style vs old-style classes, and best practice choices in actual development.

Core Concepts and Invocation Mechanisms

In Python object-oriented programming, __getattr__ and __getattribute__ are two special methods for customizing attribute access, but they differ fundamentally in their invocation timing and behavior.

__getattr__ is called only when standard attribute lookup fails, i.e., when the attempted object attribute is not found in the instance dictionary, class dictionary, or parent class chain. This makes it ideal for implementing fallback logic for missing attributes, such as dynamically creating default values or raising customized exceptions.

In contrast, __getattribute__ is triggered on every attribute access, regardless of whether the attribute exists. It completely takes over the attribute lookup process, so it must be implemented carefully to avoid infinite recursion. Typically, its implementation needs to call the base class's __getattribute__ method to perform the actual attribute resolution.

Code Examples and Behavioral Analysis

The following example demonstrates the basic usage of __getattr__:

class DynamicAttributes:
    def __init__(self):
        self.defined_attr = "defined attribute"
    
    def __getattr__(self, name):
        print(f"Calling __getattr__, requested attribute: {name}")
        value = f"dynamically generated {name}"
        setattr(self, name, value)
        return value

obj = DynamicAttributes()
print(obj.defined_attr)  # Output: defined attribute
print(obj.undefined_attr)  # Output: Calling __getattr__, requested attribute: undefined_attr dynamically generated undefined_attr

In this code, accessing the existing defined_attr does not trigger __getattr__, while accessing undefined_attr triggers the method and dynamically creates the attribute.

For __getattribute__, consider this implementation:

class ControlledAccess:
    def __init__(self):
        self.public_data = "public data"
        self._private_data = "private data"
    
    def __getattribute__(self, name):
        if name.startswith('_'):
            raise AttributeError(f"Access denied to private attribute: {name}")
        return super().__getattribute__(name)

obj = ControlledAccess()
print(obj.public_data)  # Output: public data
print(obj._private_data)  # Raises AttributeError: Access denied to private attribute: _private_data

Here, __getattribute__ intercepts all attribute accesses and enforces access control for attributes starting with an underscore.

Key Techniques to Avoid Infinite Recursion

Directly accessing self.attribute or self.__dict__['attribute'] within __getattribute__ causes recursive calls to itself, leading to RecursionError. The correct approach is to use super().__getattribute__(name) or object.__getattribute__(self, name) to delegate to the base class's lookup mechanism.

Incorrect example:

class IncorrectImplementation:
    def __getattribute__(self, name):
        # Incorrect: direct access to self.__dict__ causes recursion
        return self.__dict__[name]  # Raises RecursionError

Correct implementation:

class CorrectImplementation:
    def __init__(self):
        self.value = 42
    
    def __getattribute__(self, name):
        # Correct: access attribute via base class method
        attr_value = super().__getattribute__(name)
        print(f"Accessing attribute {name}: {attr_value}")
        return attr_value

obj = CorrectImplementation()
print(obj.value)  # Output: Accessing attribute value: 42 42

Method Coordination and Exception Handling

When both __getattribute__ and __getattr__ are defined in a class, __getattribute__ is called first. If __getattribute__ raises an AttributeError exception, Python catches it and subsequently calls __getattr__.

Example:

class CombinedMethods:
    def __init__(self):
        self.existing = "existing attribute"
    
    def __getattribute__(self, name):
        if name == "forbidden":
            raise AttributeError("Attribute access forbidden")
        return super().__getattribute__(name)
    
    def __getattr__(self, name):
        return f"fallback value: {name}"

obj = CombinedMethods()
print(obj.existing)    # Output: existing attribute
print(obj.forbidden)   # Output: fallback value: forbidden
print(obj.nonexistent) # Output: fallback value: nonexistent

In this case, accessing the forbidden attribute causes __getattribute__ to raise AttributeError, triggering __getattr__ to provide a fallback value.

Impact of New-Style vs Old-Style Classes

In Python 2, new-style classes (explicitly inheriting from object) support __getattribute__, while old-style classes do not. In Python 3, all classes are new-style, so this distinction is no longer relevant. Current development should always use new-style classes.

Practical Application Scenarios and Recommendations

Scenarios for using __getattr__:

Scenarios for using __getattribute__:

In most cases, __getattr__ is the safer and more intuitive choice, as it does not interfere with the normal attribute resolution flow. Consider using __getattribute__ only when complete control over the attribute access mechanism is necessary, and always be mindful of recursion avoidance.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.