__getattr__ 仅在 __getattribute__ 明确抛出 AttributeError 时触发,后者是属性访问的强制入口,负责完整查找流程(数据描述符→实例__dict__→类及MRO中的非数据描述符),前者不参与查找,仅作兜底。

因为 __getattr__ 本质是“兜底机制”,只在所有标准查找路径都失败后才被调用;而属性访问的第一步就是触发 __getattribute__,它负责执行整个查找流程,__getattr__ 连入场券都没有——除非 __getattribute__ 明确抛出 AttributeError。
__getattribute__ 是属性访问的必经入口
__getattribute__ 不是可选项,而是 Python 属性访问协议的强制起点。只要对象是新式类(所有继承自 object 的类,默认如此),每次写 obj.attr 或调用 getattr(obj, 'attr'),解释器都会先走 obj.__getattribute__('attr')。
这个方法内部按固定顺序尝试获取值:数据描述符 → 实例 __dict__ → 类及 MRO 中的非数据描述符/普通属性。只有当它一路查完、全部未命中,并主动抛出 AttributeError 时,Python 才会接着检查对象有没有定义 __getattr__ 并调用它。
- 没定义
__getattribute__?没关系,Python 会用object.__getattribute__默认实现,照样走完上述流程 - 你重写了
__getattribute__却忘了抛AttributeError?那__getattr__永远不会被触发 - 你在
__getattribute__里写了self.__dict__?会递归调用自己,直接RecursionError
__getattr__ 只响应明确的“未找到”信号
__getattr__ 不参与常规查找,它不看 __dict__,也不管 MRO,更不处理描述符。它的唯一触发条件是:__getattribute__ 已完成全部标准查找,确认属性不存在,并且显式 raise 了 AttributeError。
立即学习“Python免费学习笔记(深入)”;
这意味着:即使属性名拼错了、或者压根没在任何地方定义过,只要 __getattribute__ 没抛错,__getattr__ 就不会动一下。
- 常见误判:以为给实例赋值
obj.x = 1后再删掉,__getattr__就该接管——但其实只要__getattribute__没抛错,它就不会启动 - 典型用途:动态生成属性(如字段代理)、提供 fallback 值、记录缺失访问日志
- 注意:如果类里同时有
@property和__getattr__,前者是类字典里的非数据描述符,优先级低于实例__dict__,但远高于__getattr__
为什么不能跳过 __getattribute__ 直接到 __getattr__?
这不是设计疏漏,而是语义隔离的需要。Python 把“查找失败”和“如何兜底”拆成两个阶段:__getattribute__ 负责判断“找没找到”,__getattr__ 负责回答“找不到怎么办”。强行绕过前者,等于破坏整个属性协议的一致性。
比如 len(obj) 会隐式访问 obj.__len__,这个过程也必须经过 __getattribute__ ——否则你无法统一拦截所有特殊方法调用。同样,isinstance、hasattr 等内置函数也都依赖这套机制。
-
hasattr(obj, 'x')底层就是 try/exceptAttributeError,它照样走__getattribute__,再可能进__getattr__ - 如果你在
__getattribute__里对某些属性返回默认值(比如log_level),那hasattr(obj, 'log_level')就永远为True,哪怕你没在任何字典里存它 - 想让
__getattr__生效,唯一可靠方式是确保__getattribute__对目标属性名最终抛出AttributeError
真正容易被忽略的点在于:很多人以为重写 __getattr__ 就能接管所有“不存在”的属性,却没意识到 __getattribute__ 的存在本身已经决定了“不存在”的定义权——它才是那个说“算你没找到”的人。


















