__getattr__ / __getattribute__

Updated March 27, 2026 · Dunder Methods
python __getattr__ __getattribute__ dunder-methods

Python provides two hooks for intercepting attribute access on instances: __getattribute__ and __getattr__. Together they give you full control over how attributes are retrieved from an object.

__getattribute__ is called on every attribute lookup, before Python checks the instance dictionary, class attributes, or descriptors. __getattr__ is called only as a fallback — when __getattribute__ raises AttributeError.

__getattribute__ — Every Attribute Access

__getattribute__ intercepts all attribute lookups. It receives the attribute name as a string and must return the value or raise AttributeError.

def __getattribute__(self, name: str) -> Any:
    ...

Because it runs on every access, it is the only place you can use to log or validate all attribute lookups:

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

    def __getattribute__(self, name):
        print(f"accessing '{name}'")
        return object.__getattribute__(self, name)


t = Tracked(42)
print(t.value)
print(t.value + 1)
# output: accessing 'value'
# output: 42
# output: accessing 'value'
# output: 43

__getattr__ — Missing Attributes Only

__getattr__ is invoked only when __getattribute__ raises AttributeError. It never fires for attributes that already exist through normal lookup.

def __getattr__(self, name: str) -> Any:
    ...

This makes it ideal for lazy attributes, proxy objects, and virtual attributes:

class Lazy:
    def __init__(self):
        self._expensive = None

    def __getattr__(self, name):
        if name == "data":
            print("computing data...")
            self._expensive = list(range(10))
            return self._expensive
        raise AttributeError(name)


obj = Lazy()
print(obj.data)   # computing data...
print(obj.data)   # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  (cached, no recompute)
# output: computing data...
# output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Lookup Order

The interaction between the two methods follows a strict order:

  1. __getattribute__ is called for every attribute access
  2. If it raises AttributeError, __getattr__ is called as fallback

If __getattribute__ returns without raising AttributeError, __getattr__ is never invoked:

class Example:
    def __init__(self):
        self.known = "exists"

    def __getattribute__(self, name):
        if name == "known":
            return object.__getattribute__(self, name)
        # Does NOT fall through to __getattr__ — returns normally
        return None

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


e = Example()
print(e.known)     # exists  (handled by __getattribute__)
print(e.unknown)   # None  (no print — __getattr__ not called!)
# output: exists
# output: None

Infinite Recursion Trap

Accessing any attribute inside __getattribute__ triggers __getattribute__ again, leading to infinite recursion:

# BROKEN — RecursionError
def __getattribute__(self, name):
    return self._cache[name]

The fix is to use object.__getattribute__ to bypass the hook:

# CORRECT
def __getattribute__(self, name):
    if name.startswith("_"):
        raise AttributeError(name)
    return object.__getattribute__(self, name)

__getattr__ does not have this problem — accessing self.__dict__ inside __getattr__ is safe because the attribute already exists.

hasattr, getattr, and vars() Behavior

Both hasattr and getattr trigger these hooks normally:

Operation__getattribute__ called?__getattr__ called?
obj.attrYesYes, if first raises AttributeError
getattr(obj, "attr")YesYes, if first raises AttributeError
hasattr(obj, "attr")YesYes, if first raises AttributeError

hasattr catches AttributeError internally and returns True or False, but both hooks still fire during the lookup.

vars(obj) returns the instance __dict__ and does not invoke either hook.

__getattribute__ + __slots__

__slots__ removes the instance __dict__, but __getattribute__ still intercepts all attribute access before Python checks the slots:

class Point:
    __slots__ = ("x", "y")

    def __getattribute__(self, name):
        print(f"accessing '{name}'")
        return object.__getattribute__(self, name)


p = Point()
p.x = 1
p.y = 2
print(p.x)
print(p.y)
# output: accessing 'x'
# output: 1
# output: accessing 'y'
# output: 2

If __getattribute__ raises AttributeError for an attribute not in __slots__, __getattr__ is called as fallback — even with __slots__ defined.

Decision Summary

Use __getattribute__ when you need to intercept all attribute access — for logging, validation, or gating every lookup. Use __getattr__ when you only need to handle attributes that do not exist — for lazy initialization, proxying, or virtual attributes.

Scenario__getattribute____getattr__
Log all accessYesNo
Validate attribute namesYesNo
Gate specific attributesYesNo
Lazy / virtual attributesYesYes (simpler)
Proxy / delegationYesYes
Catch-all for unknown attrsNoYes

Summary Table

MethodCalled when attribute…Can raise AttributeError?
__getattribute__Exists or not — always calledYes
__getattr__Does not exist — fallback onlyYes

See Also

  • __del__ — cleanup logic when an object is deleted
  • __setitem__ — attribute-style item assignment