Skip to main content

__getattr__ and __getattribute__: Custom Attribute Access

The magic methods __getattr__ and __getattribute__ allow you to intercept and customize how attributes are accessed on an object. __getattribute__ is invoked for every attribute access and is called first; __getattr__ is called only when an attribute is not found through normal lookup. Confusing the two is a common pitfall that leads to infinite recursion and subtle bugs. Understanding when to use each—and how they interact with Python's attribute-resolution chain—is essential for building proxy objects, lazy-loading systems, and frameworks that wrap third-party APIs.

__getattribute__ vs. __getattr__: When Each Is Called

__getattribute__ is the lower-level mechanism: Python calls it for every attribute access on an instance, even if the attribute exists. __getattr__ is called only as a fallback when an attribute is not found through the normal chain. This distinction is critical: implementing __getattribute__ on a high-frequency accessor is expensive; implementing __getattr__ is cheap because it's only invoked on cache misses.

Here's the lookup order: (1) instance __dict__, (2) descriptor in class, (3) class __dict__, (4) parent classes, (5) if all fail, call __getattr__. If you override __getattribute__, you short-circuit this entire chain unless you explicitly call super().__getattribute__().

Using __getattr__ for Lazy Loading

__getattr__ is most commonly used to provide fallback behavior or lazy-load attributes on first access. Here's a practical example: a wrapper around an HTTP API client that loads resource data only when accessed:

import requests

class APIResource:
"""Lazy-loading wrapper around a remote API resource."""

def __init__(self, endpoint, resource_id):
self.endpoint = endpoint
self.resource_id = resource_id
self._data = None # Will be loaded on first access

def __getattr__(self, name):
# Called only when 'name' is not found in instance or class dict
if name.startswith('_'):
raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")

# Lazy-load data from API on first access
if self._data is None:
response = requests.get(f"{self.endpoint}/{self.resource_id}")
self._data = response.json()

# Return the attribute from the loaded data
if name in self._data:
return self._data[name]

raise AttributeError(f"Resource has no field '{name}'")


# Usage
user = APIResource("https://api.example.com/users", 42)
print(user.name) # Makes HTTP request, returns name from JSON
print(user.email) # Uses cached data, no second request

This pattern is used in database query libraries (SQLAlchemy lazy-loads relationships) and API clients (boto3, google-cloud-python) to defer expensive operations until needed.

Using __getattribute__ for Proxies and Introspection

__getattribute__ is powerful but dangerous: every attribute access goes through it, including access to methods and special attributes. Misuse causes infinite recursion. Here's a safe proxy pattern:

class TracingProxy:
"""Logs every attribute access on a wrapped object."""

def __init__(self, target):
# Use object.__setattr__ to bypass __setattr__ interception
object.__setattr__(self, '_target', target)
object.__setattr__(self, '_access_log', [])

def __getattribute__(self, name):
# Access _target and _access_log using object's __getattribute__ to avoid recursion
target = object.__getattribute__(self, '_target')
log = object.__getattribute__(self, '_access_log')

# Avoid recursing on special attributes
if name in ('_target', '_access_log'):
return object.__getattribute__(self, name)

# Log the access
log.append(name)

# Delegate to the real object
return getattr(target, name)

def access_history(self):
return object.__getattribute__(self, '_access_log')


# Usage
obj = TracingProxy({'key': 'value'})
result = obj.get('key')
print(obj.access_history()) # Output: ['get']

The key to avoiding recursion is using object.__getattribute__() to access your own internal state (_target, _access_log), bypassing your custom logic. This is a defensive pattern essential when implementing proxies or aspect-oriented code.

Delegation Pattern: Wrapping Objects Cleanly

A common metaprogramming task is wrapping an object to add behavior without modifying its type. The delegation pattern uses __getattr__ to forward unknown attributes:

from functools import wraps

class CachedWrapper:
"""Wraps an object and caches method results."""

def __init__(self, target, cache_methods=None):
self._target = target
self._cache = {}
self._cache_methods = cache_methods or []

def __getattr__(self, name):
# Forward to the wrapped object
attr = getattr(self._target, name)

# If it's a cached method, wrap it with caching logic
if name in self._cache_methods and callable(attr):
@wraps(attr)
def cached_call(*args, **kwargs):
# Create a cache key from method name and arguments
key = (name, args, tuple(sorted(kwargs.items())))
if key not in self._cache:
self._cache[key] = attr(*args, **kwargs)
return self._cache[key]
return cached_call

return attr


class DataFetcher:
def get_user(self, user_id):
print(f"Fetching user {user_id}...")
return {"id": user_id, "name": "Alice"}


# Usage
fetcher = DataFetcher()
cached_fetcher = CachedWrapper(fetcher, cache_methods=['get_user'])

print(cached_fetcher.get_user(1)) # Fetches: "Fetching user 1..."
print(cached_fetcher.get_user(1)) # Returns cached result, no print

This pattern enables adding cross-cutting concerns (caching, logging, rate-limiting) to existing objects without modifying their source code—a powerful technique in frameworks.

Pitfalls: Infinite Recursion and Performance

The most common bug with __getattribute__ is infinite recursion. If your custom __getattribute__ accesses self.x, and self.x doesn't exist, Python calls your __getattribute__ again, which calls self.x, and so on. Always use object.__getattribute__() to access your own state or explicitly call super().__getattribute__().

Second, implementing __getattribute__ slows down every attribute access. A tight loop calling an attribute 1 million times will be noticeably slower if you override __getattribute__. Prefer __getattr__ when possible, or use __slots__ to reduce attribute-lookup overhead.

Comparison: __getattr__ vs. __getattribute__ vs. Properties

MechanismCalled WhenUse CaseCost
__getattr__Attribute not found (fallback)Lazy loading, dynamic attributes, API proxiesLow (fallback only)
__getattribute__Every attribute accessProxies, logging, introspectionHigh (on every access)
@property descriptorSpecific named attributeComputed properties, validationLow (specific attribute)

Key Takeaways

  • __getattr__ is called only when an attribute is not found; use it for fallback behavior and lazy loading.
  • __getattribute__ is called for every attribute access; use it sparingly and always guard against infinite recursion with object.__getattribute__().
  • Delegation patterns with __getattr__ cleanly add behavior to wrapped objects without modifying their type.
  • Proxies and API wrappers are idiomatic uses of __getattr__; most developers never need to override __getattribute__.

Frequently Asked Questions

Why does my __getattribute__ implementation cause infinite recursion?

If you access self.attr inside __getattribute__, Python calls your custom __getattribute__ again, creating a loop. Always use object.__getattribute__(self, 'attr') to access your own state, bypassing your custom logic.

Should I use __getattr__ or @property for computed attributes?

Use @property if the attribute is known at class definition time and belongs to a single instance. Use __getattr__ if the attribute is dynamic, unknown at class time, or shared across many attributes (e.g., forwarding to a wrapped object).

How does __getattr__ interact with __slots__?

If a class uses __slots__ and you define __getattr__, it will still be called for any attribute not in __slots__. However, instance __dict__ is not available with __slots__, so your __getattr__ cannot use self.__dict__.

Can I use __getattr__ on a __dict__ entry for introspection?

Yes. If you access self.__dict__ inside __getattr__, Python calls it recursively because looking up __dict__ triggers __getattr__. Use object.__getattribute__(self, '__dict__') to safely access the instance dictionary.

Further Reading