getattr 和 getattribute 的执行顺序与无限递归陷阱

9次阅读

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

getattr 和 getattribute 的执行顺序与无限递归陷阱

当访问对象属性时,__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_attrhasattr(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 正常返回 42obj.missing__getattr__ 返回 "dynamic_missing",全程无递归。

为什么 getattr(obj, 'x') 也会触发 __getattribute__?

因为 getattr() 是普通函数,其内部就是靠 obj.x 机制实现的(即调用 obj.__getattribute__('x'))。所以即使你用 getattr,只要类定义了 __getattribute__,它一样会被拦截。想绕过自定义逻辑?只能用 object.__getattribute__(obj, 'x')

text=ZqhQzanResources