__getattribute__总是优先调用,拦截所有属性访问;仅当它抛出AttributeError且类定义了__getattr__时,才触发后者作为兜底;在__getattribute__中直接访问自身属性(如self.__dict__)会导致无限递归,必须通过object.__getattribute__(self, name)安全委托。
当访问对象属性时,__getattribute__ 总是先被调用;如果它没找到属性,且类中定义了 __getattr__,才会触发 __getattr__。但正因 __getattribute__ 拦截一切属性访问,若在里面直接访问自身属性(比如 self.__dict__),就可能引发无限递归。
__getattribute__ 是属性访问的“第一道门”,无论属性是否存在、是否为特殊方法、是否是实例变量或类变量,只要通过点号(obj.attr)或 getattr(obj, 'attr') 访问,它都会被调用。只有当它显式抛出 AttributeError(或未捕获异常导致传播),Python 才会继续尝试调用 __getattr__ —— 后者只负责处理“真正缺失”的属性。
__getattribute__ 且没抛错 → 不进 __getattr__
__getattribute__ 但抛了 AttributeError → 进 __getattr__
__getattribute__ → 直接走默认查找逻辑,不触发 __getattr__(除非属性真找不到)常见错误是在重写的 __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')
下面是一个既能记录访问、又能避免递归、还能兜底到 __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 机制实现的(即调用 obj.__getattribute__('x'))。所以即使你用 getattr,只要类定义了 __getattribute__,它一样会被拦截。想绕过自定义逻辑?只能用 object.__getattribute__(obj, 'x')。