__getattribute__总是优先调用,拦截所有属性访问;仅当它抛出AttributeError且类定义了__getattr__时,才触发后者作为兜底;在__getattribute__中直接访问自身属性(如self.__dict__)会导致无限递归,必须通过object.__getattribute__(self, name)安全委托。

当访问对象属性时,__getattribute__ 总是先被调用;如果它没找到属性,且类中定义了 __getattr__,才会触发 __getattr__。但正因 __getattribute__ 拦截一切属性访问,若在里面直接访问自身属性(比如 self.__dict__),就可能引发无限递归。
执行顺序:__getattribute__ 优先,__getattr__ 是兜底
__getattribute__ 是属性访问的“第一道门”,无论属性是否存在、是否为特殊方法、是否是实例变量或类变量,只要通过点号(obj.attr)或 getattr(obj, 'attr') 访问,它都会被调用。只有当它显式抛出 AttributeError(或未捕获异常导致传播),Python 才会继续尝试调用 __getattr__ —— 后者只负责处理“真正缺失”的属性。
- 有
__getattribute__且没抛错 → 不进__getattr__ - 有
__getattribute__但抛了AttributeError→ 进__getattr__ - 没定义
__getattribute__→ 直接走默认查找逻辑,不触发__getattr__(除非属性真找不到)
无限递归陷阱:别在 __getattribute__ 里直接访问 self 的任何属性
常见错误是在重写的 __getattribute__ 中写 self.__dict__、self.some_attr 或 hasattr(self, 'x') —— 这些都会再次触发 __getattribute__,形成死循环。
- ✅ 安全做法:用
object.__getattribute__(self, name)绕过自定义逻辑,委托给父类实现 - ✅ 查找实例字典:用
self.__dict__.get(name)(但注意__dict__本身也要用object.__getattribute__获取) - ❌ 危险写法:
return self.__dict__[name]、if self.debug:、hasattr(self, 'cache')
实用示例:带日志的属性访问 + 安全 fallback
下面是一个既能记录访问、又能避免递归、还能兜底到 __getattr__ 的写法:
class LoggedAttr:
def __init__(self):
self._value = 42
def __getattribute__(self, name):
print(f"Accessing: {name}")
try:
# 用 object.__getattribute__ 安全获取
return object.__getattribute__(self, name)
except AttributeError:
# 主动抛出,让 __getattr__ 有机会介入
raise
def __getattr__(self, name):
print(f"__getattr__ handling missing: {name}")
return f"dynamic_{name}"此时 obj._value 正常返回 42,obj.missing 走 __getattr__ 返回 "dynamic_missing",全程无递归。
为什么 getattr(obj, 'x') 也会触发 __getattribute__?
因为 getattr() 是普通函数,其内部就是靠 obj.x 机制实现的(即调用 obj.__getattribute__('x'))。所以即使你用 getattr,只要类定义了 __getattribute__,它一样会被拦截。想绕过自定义逻辑?只能用 object.__getattribute__(obj, 'x')。










