__getattr__ / __getattribute__

Python lets a class intercept attribute access entirely: __getattribute__ runs on every lookup, while __getattr__ is a fallback that only fires when normal lookup fails.

Normally, obj.attr resolves through a well-defined lookup chain: instance __dict__, then the class and its MRO, with descriptors (like @property) taking priority at certain points. Two dunder methods let a class hook into and override that process: __getattribute__, which intercepts EVERY attribute access unconditionally, and __getattr__, which is called only as a fallback when normal lookup fails to find the attribute anywhere. Understanding the difference — and the recursion hazards each one invites — is essential before using either for proxies, lazy attributes, or dynamic APIs.

Normal Attribute Lookup (Baseline)

Before overriding anything, it helps to know what obj.attr does by default: Python calls object.__getattribute__(obj, 'attr'), which checks (roughly, in this priority) data descriptors found via the class's MRO, then the instance's __dict__, then non-data descriptors and plain class attributes found via the MRO, raising AttributeError if nothing is found anywhere. __getattr__ and __getattribute__ let you insert custom behavior into or around this chain.

__getattr__: The Fallback Hook

__getattr__(self, name) is called ONLY when normal attribute lookup has already failed to find name through the usual channels (instance dict, class, MRO). It is never called for an attribute that already exists — it exists specifically to handle the 'not found' case, letting you compute, redirect, or synthesize a value instead of letting AttributeError propagate immediately.

cfg._data is a real instance attribute found by normal lookup, so __getattr__ never runs for it; cfg.host is not, so __getattr__ is consulted as a fallback.

A __getattr__ that can't produce a value must raise AttributeError (not return None or some sentinel) — many parts of Python (hasattr, getattr with a default, pickling, copy) rely on AttributeError specifically to detect 'attribute genuinely missing'.

class Config:
    def __init__(self):
        self._data = {"host": "localhost", "port": 8080}

    def __getattr__(self, name):
        # Only called because normal lookup didn't find 'name' anywhere
        try:
            return self._data[name]
        except KeyError:
            raise AttributeError(f"'Config' object has no attribute {name!r}")

cfg = Config()
print(cfg.host)      # localhost -- __getattr__ fires, 'host' isn't a real attribute
print(cfg._data)     # {'host': ..., 'port': ...} -- found normally, __getattr__ NOT called

__getattribute__: The Universal Interceptor

__getattribute__(self, name) is called for EVERY attribute access on the object, unconditionally — including ones that would otherwise be found immediately in the instance __dict__. Overriding it gives total control over attribute resolution, but that power comes with responsibility: you're replacing Python's entire default lookup mechanism, not adding to it.

Every single attribute access — even for value, which exists as a normal instance attribute — goes through the overridden __getattribute__ first.

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

    def __getattribute__(self, name):
        print(f"accessing {name!r}")
        return object.__getattribute__(self, name)  # delegate to the real lookup

obj = Logger(42)
print(obj.value)
# accessing 'value'
# 42

Recursion Hazards

The single most common bug with both hooks is infinite recursion. Inside __getattribute__, writing self.name or getattr(self, name) to fetch the value calls __getattribute__ again (since ALL attribute access goes through it), recursing forever until RecursionError. The fix is to delegate to the base implementation explicitly: object.__getattribute__(self, name). Inside __getattr__, accidentally accessing an attribute that also doesn't exist (e.g. via self.__dict__ misuse, or referencing self._data before it's set) can similarly trigger infinite fallback recursion.

A very common real-world trap: __getattr__ referencing self._data before __init__ has set it (e.g. during unpickling, or if __init__ itself raises partway through) causes __getattr__ to be called again looking for _data, which also isn't found, recursing until RecursionError. Guard against this by checking name or using object.__getattribute__ for internal state.

class Broken:
    def __getattribute__(self, name):
        return self.__dict__[name]   # self.__dict__ ALSO goes through __getattribute__!

# Broken().anything  # RecursionError: maximum recursion depth exceeded

class Fixed:
    def __getattribute__(self, name):
        return object.__getattribute__(self, "__dict__")[name]  # bypasses the override

Interaction with __setattr__

__getattr__/__getattribute__ only intercept READS. Attribute assignment (obj.attr = value) goes through the separate __setattr__ hook, which has its own default behavior (writing to __dict__) and its own recursion hazard (self.attr = x inside an overridden __setattr__ recurses unless you delegate via object.__setattr__(self, name, value)). Building a fully custom attribute system (a proxy, an ORM-like model) typically requires overriding __setattr__ alongside whichever __getattr__/__getattribute__ hook is used.

Proxy Patterns

A common legitimate use of __getattr__ is building a lightweight proxy or wrapper object: it forwards any attribute access it doesn't itself define to a wrapped inner object, transparently extending or adapting that object's interface.

_wrapped is a real attribute on LoggingProxy, so normal lookup finds it and __getattr__ is skipped; append isn't defined on LoggingProxy, so __getattr__ forwards to the wrapped list.

class LoggingProxy:
    def __init__(self, wrapped):
        self._wrapped = wrapped

    def __getattr__(self, name):
        # Only reached for names not found on LoggingProxy itself (e.g. not '_wrapped')
        attr = getattr(self._wrapped, name)
        if callable(attr):
            def logged(*args, **kwargs):
                print(f"calling {name}")
                return attr(*args, **kwargs)
            return logged
        return attr

proxy = LoggingProxy([1, 2, 3])
print(proxy.append)     # a wrapped bound method (via __getattr__)
proxy.append(4)         # calling append
print(proxy._wrapped)   # [1, 2, 3, 4] -- found directly, __getattr__ never involved

Dynamic Attribute APIs

__getattr__ is also used for computing attributes lazily/on demand instead of storing every possible value up front — useful when there are many possible attribute names, or when computing a value is expensive and should only happen if actually requested.

class LazyGreeting:
    def __getattr__(self, name):
        if name.startswith("greet_"):
            language = name.removeprefix("greet_")
            greetings = {"en": "Hello", "fr": "Bonjour", "es": "Hola"}
            if language in greetings:
                return greetings[language]
        raise AttributeError(name)

g = LazyGreeting()
print(g.greet_en)  # Hello
print(g.greet_fr)  # Bonjour

Module-Level __getattr__

Since Python 3.7 (PEP 562), a module can define its own module-level __getattr__(name) function to intercept attribute access on the module itself (e.g. mymodule.some_name) when the name isn't already a module global. This is commonly used for deprecation warnings on renamed exports and for lazy-importing expensive submodules only when actually accessed.

# inside mymodule.py
import warnings

def __getattr__(name):
    if name == "old_function":
        warnings.warn("old_function is deprecated, use new_function", DeprecationWarning)
        return new_function
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

def new_function():
    return "new behavior"

Edge Cases

Dunder methods invoked implicitly by the interpreter for special syntax (e.g. len(obj) calling __len__, for calling __iter__) are looked up on the TYPE, bypassing instance __getattr__/__getattribute__ for that implicit lookup in CPython — this is a well-known gotcha: defining __getattr__ to synthesize __len__ dynamically will not make len(obj) work. getattr(obj, name, default) suppresses AttributeError and returns default instead — but only AttributeError specifically; any other exception raised inside __getattr__/__getattribute__ propagates normally.

class Dynamic:
    def __getattr__(self, name):
        if name == "__len__":
            return lambda: 5
        raise AttributeError(name)

d = Dynamic()
print(d.__len__())  # 5 -- explicit call works, __getattr__ is consulted
# print(len(d))      # TypeError: object of type 'Dynamic' has no len() -- implicit dunder lookup bypasses __getattr__

Best Practices

Prefer __getattr__ over __getattribute__ whenever possible — it only handles the 'missing' case, so it can't accidentally break normal attribute access or introduce a recursion bug for attributes that already work fine. Reach for __getattribute__ only when you truly need to intercept every access uniformly (e.g. transparent logging/auditing of all reads, or a strict access-control layer). Always raise AttributeError (with a clear message) when a __getattr__/__getattribute__ override can't produce a value — never return None silently, which breaks hasattr() and masks real bugs. Delegate to object.__getattribute__/object.__setattr__ explicitly whenever accessing self's own state from inside either override, to avoid recursion.

Key Takeaways

__getattr__ fires only as a fallback when normal attribute lookup has already failed to find the name.

__getattribute__ fires unconditionally for every attribute access, including ones that already succeed normally — override it only when you truly need that.

Both hooks risk infinite recursion if they access self's attributes through normal dotted access instead of delegating to object.__getattribute__.

A __getattr__/__getattribute__ override that cannot resolve a name must raise AttributeError, not return None, to keep hasattr() and other introspection working correctly.

Reads go through __getattr__/__getattribute__; writes go through the separate __setattr__ hook.

Implicit dunder lookups triggered by special syntax (len(), for, etc.) look up the method on the type, bypassing instance-level __getattr__/__getattribute__.

Module-level __getattr__ (PEP 562, 3.7+) intercepts attribute access on a module itself, commonly used for deprecations and lazy imports.

class Broken: def __getattribute__(self, name): return self.__dict__[name] # self.__dict__ recurses through __getattribute__ again class Fixed: def __getattribute__(self, name): return object.__getattribute__(self, name) # delegate to the base implementation

Accessing self.anything inside __getattribute__ calls __getattribute__ again; always delegate through object.__getattribute__ (or super().__getattribute__) to avoid infinite recursion.

class Config: def __getattr__(self, name): return self._data.get(name) # returns None instead of raising -- breaks hasattr() class Config: def __getattr__(self, name): if name in self._data: return self._data[name] raise AttributeError(f"'Config' object has no attribute {name!r}")

Returning None for a missing attribute instead of raising AttributeError makes hasattr(obj, name) incorrectly return True and silently hides typos.

class Model: def __init__(self, fields): for k, v in fields.items(): self._fields = fields # bug: overwritten each iteration, and read before set def __getattr__(self, name): return self._fields[name] # RecursionError if __init__ hasn't run yet (e.g. during unpickling) class Model: def __init__(self, fields): self.__dict__["_fields"] = fields # set directly, bypassing any custom __setattr__ def __getattr__(self, name): fields = self.__dict__.get("_fields", {}) if name in fields: return fields[name] raise AttributeError(name)

If __getattr__ references an internal attribute (like _fields) that might not exist yet (e.g. before __init__ runs, during unpickling), looking it up via self._fields re-triggers __getattr__ and recurses; guard with self.__dict__.get(...) instead.

What is the key difference in WHEN __getattr__ and __getattribute__ are called?

__getattribute__ is called for every attribute access, unconditionally. __getattr__ is called only as a fallback, after normal lookup (which includes an overridden __getattribute__, if any) has already failed to find the attribute.

Why does writing return self.name inside an overridden __getattribute__ cause infinite recursion?

Because self.name is itself an attribute access, which goes through __getattribute__ again (that's the whole point of overriding it — it intercepts everything) — recursing until RecursionError. The fix is to call object.__getattribute__(self, name) directly.

Why must a __getattr__ override raise AttributeError rather than return None when it can't resolve a name?

Many parts of Python rely on AttributeError specifically to signal 'this attribute genuinely doesn't exist' — hasattr(), getattr(obj, name, default), pickling, and copy all depend on that exception type. Returning None instead makes those mechanisms behave incorrectly (e.g. hasattr returns True for something that doesn't really exist).

Why doesn't defining __getattr__ to compute __len__ dynamically make len(obj) work?

Implicit dunder lookups triggered by special syntax like len() are resolved on the object's TYPE (bypassing instance-level __getattr__/__getattribute__), so a dynamically-synthesized __len__ via __getattr__ is invisible to len(), even though calling obj.__len__() explicitly would work.

Which hook — __getattr__ or __getattribute__ — intercepts attribute ASSIGNMENT (obj.attr = value)?

Neither — assignment goes through the separate __setattr__ hook. __getattr__/__getattribute__ only apply to attribute reads.

In a proxy class using __getattr__ to forward to a wrapped object, why does accessing the proxy's own _wrapped attribute never trigger __getattr__?

__getattr__ only fires when normal lookup fails. Since _wrapped is a genuine instance attribute set in __init__, normal lookup finds it directly in the instance __dict__, so the fallback __getattr__ is never consulted for it.

What does module-level __getattr__ (PEP 562) let you do, and since which Python version?

Since Python 3.7, a module can define its own __getattr__(name) function to intercept access to names not already present as module globals — commonly used for raising deprecation warnings on renamed exports or lazily importing expensive submodules only when actually accessed.

Write a class ReadOnlyDict wrapping a dict, using __getattr__ to expose its keys as attributes (read-only), raising AttributeError for missing keys.

class ReadOnlyDict: def __init__(self, data: dict): self.__dict__["_data"] = data # bypass any future __setattr__ override def __getattr__(self, name): data = self.__dict__["_data"] if name in data: return data[name] raise AttributeError(f"'ReadOnlyDict' object has no attribute {name!r}") r = ReadOnlyDict({"x": 1, "y": 2}) print(r.x, r.y) # 1 2 print(hasattr(r, "z")) # False

_data is stashed directly in __dict__ during __init__ so it's found by normal lookup (never triggering __getattr__ for it); any other name falls through to __getattr__, which looks it up in _data or raises AttributeError, making hasattr behave correctly.

What gets printed by these two objects' attribute accesses?

Trace prints get __class__ — __getattribute__ fires for every access including ones that succeed normally. Fallback prints fallback for anything then default (from the print) — __getattr__ only fires because anything isn't found by normal lookup, and it returns the string 'default'.

Write a lazy-attribute class LazyImports using module-style __getattr__ on an instance to compute and CACHE an expensive value the first time it's accessed, returning the cached value on subsequent accesses.

class LazyImports: def __getattr__(self, name): if name == "big_data": print("computing big_data...") value = list(range(1000)) # pretend this is expensive self.__dict__["big_data"] = value # cache directly in __dict__ return value raise AttributeError(name) obj = LazyImports() obj.big_data # computing big_data... (first access) obj.big_data # (no print -- found directly in __dict__ now, __getattr__ skipped)

The first access misses normal lookup, triggers __getattr__, computes the value, and stores it directly into self.__dict__. Because it's now a real instance attribute, every subsequent access is found by normal lookup and __getattr__ is never called again for that name — a classic memoization pattern.

class Trace:
    def __getattribute__(self, name):
        print(f"get {name}")
        return object.__getattribute__(self, name)

class Fallback:
    def __getattr__(self, name):
        print(f"fallback for {name}")
        return "default"

t = Trace()
t.__class__  # accessing an attribute that exists
f = Fallback()
print(f.anything)