__getattr__ / __getattribute__
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:
__getattribute__is called for every attribute access- 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.attr | Yes | Yes, if first raises AttributeError |
getattr(obj, "attr") | Yes | Yes, if first raises AttributeError |
hasattr(obj, "attr") | Yes | Yes, 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 access | Yes | No |
| Validate attribute names | Yes | No |
| Gate specific attributes | Yes | No |
| Lazy / virtual attributes | Yes | Yes (simpler) |
| Proxy / delegation | Yes | Yes |
| Catch-all for unknown attrs | No | Yes |
Summary Table
| Method | Called when attribute… | Can raise AttributeError? |
|---|---|---|
__getattribute__ | Exists or not — always called | Yes |
__getattr__ | Does not exist — fallback only | Yes |
See Also
__del__— cleanup logic when an object is deleted__setitem__— attribute-style item assignment