getattr vs getattribute in Python

Python provides two primary magic methods for intercepting attribute access on objects: __getattr__ and __getattribute__. While both methods allow developers to customize how attributes are retrieved, they operate at fundamentally different stages of the lookup process. This article breaks down the mechanics of each method, illustrates their execution flow, highlights the risk of infinite recursion, and clarifies when to use one over the other.

The Core Difference

The primary distinction between the two methods lies in when they are invoked by the Python interpreter:

How __getattribute__ Works

Whenever you access an attribute via dot notation (e.g., obj.name), Python implicitly calls obj.__getattribute__('name'). Because it executes on every single attribute lookup, it provides complete control over attribute resolution.

The Recursion Trap

Because __getattribute__ intercepts all lookups, referencing an attribute inside it using self.attr or self.__dict__ triggers another call to __getattribute__, resulting in an infinite recursion error (RecursionError).

To safely access attributes inside __getattribute__, you must route the lookup through the base class implementation using super():

class Interceptor:
    def __init__(self, value):
        self.value = value

    def __getattribute__(self, name):
        print(f"Intercepting access to: {name}")
        # Safe access using super()
        return super().__getattribute__(name)

obj = Interceptor(42)
print(obj.value)
# Output:
# Intercepting access to: value
# 42

How __getattr__ Works

__getattr__ is designed for handling missing attributes. If Python resolves an attribute successfully through standard lookup (or via __getattribute__), __getattr__ is never invoked.

This makes __getattr__ much safer and more performant for typical use cases, such as dynamic delegation or providing default values:

class DynamicFallback:
    def __init__(self):
        self.existing = "I exist"

    def __getattr__(self, name):
        print(f"Attribute '{name}' not found. Handling dynamically.")
        return f"default_{name}"

obj = DynamicFallback()

# Normal lookup: __getattr__ is NOT called
print(obj.existing)  # Output: I exist

# Missing attribute: __getattr__ IS called
print(obj.missing)   
# Output:
# Attribute 'missing' not found. Handling dynamically.
# default_missing

The Attribute Lookup Order

When an attribute obj.x is accessed, Python follows this sequence:

  1. obj.__getattribute__('x') is called.
  2. The default implementation checks descriptors, the instance __dict__, and the class hierarchy.
  3. If the attribute is found, it is returned.
  4. If the attribute is not found, Python raises an AttributeError.
  5. If defined, obj.__getattr__('x') catches the AttributeError and handles the request.
  6. If __getattr__ is not implemented, the AttributeError bubbles up to the caller.

Practical Comparison

Feature __getattr__ __getattribute__
Invocation Only when attribute is not found On every attribute access
Performance Impact Negligible on existing attributes Overhead on every lookup
Recursion Risk Low High
Base Class Call Generally not required Required via super()
Common Use Case Proxies, adapters, dynamic attributes Profiling, security wrappers, auditing

When to Use Which

Use __getattr__ in the vast majority of scenarios. It is the appropriate choice when implementing proxies, delegating method calls to wrapped objects, or handling lazy-loaded attributes. It leaves Python's fast, default lookup mechanisms untouched for attributes that exist.

Use __getattribute__ only when absolute control over the object's namespace is required. Common scenarios include building debugging tools, creating security proxies that hide existing attributes, or deeply integrating with custom data-binding frameworks.