
Python魔法方法的查找顺序,本质上是对象属性访问机制的一部分,核心由 __getattribute__ 控制,但实际调用逻辑更依赖于类型系统和描述符协议。它不是简单的“从实例到类再到父类”的线性搜索,而是一套融合了描述符、继承链与特殊规则的解析流程。
一、魔法方法不走常规属性查找路径
普通属性(如 obj.x)会依次触发:
- 实例字典
__dict__ - 类及其 MRO 链上的属性(含描述符)
- 最终回退到
__getattr__(如果定义)
但魔法方法(如 __add__、__len__、__str__)**几乎从不在实例字典中查找**。Python 解释器在编译或运行时,对已知魔法方法名有硬编码优化:直接跳过实例,只在类及其 MRO 中查找,并且**优先检查定义该方法的类是否为新式类(即继承自 object)**。
例如:len(obj) 不会看 obj.__dict__ 里有没有 __len__,而是直接查 type(obj).__len__,再沿 MRO 向上找。
立即学习“Python免费学习笔记(深入)”;
二、MRO 决定继承查找顺序,但描述符可介入
一旦确定在类体系中查找,就按 MRO(Method Resolution Order)顺序遍历。比如:
PHP5学习对象教程由美国人古曼兹、贝肯、瑞桑斯编著,简张桂翻译,电子工业出版社于2007年12月1日出版的关于PHP5应用程序的技术类图书。该书全面介绍了PHP 5中的新功能、编程方法及设计模式,还分析阐述了PHP 5中新的数据库连接处理、错误处理和XML处理等机制,帮助读者系统了解、熟练掌握和高效应用PHP。
class A: pass class B(A): pass class C(A): pass class D(B, C): pass print(D.__mro__) # (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
当调用 d.__add__(other) 时,解释器按此元组顺序查找第一个定义了 __add__ 的类。
注意:若某处定义的是**数据描述符**(同时有 __get__ 和 __set__),它会拦截访问;但魔法方法极少被实现为描述符,因此通常不构成干扰。
三、内置类型与用户类的差异处理
对内置类型(如 list、int)的实例,其魔法方法绑定是 C 层硬编码的,不经过 Python 层的 MRO 查找。例如:
-
[1,2].__len__()直接调用 C 函数list_len - 你无法通过给
list实例动态设置__len__来覆盖行为
而用户自定义类则完全走 Python 的查找逻辑——这也是为什么重载 __add__ 必须定义在类中,而非实例上。
四、特殊情况:__getattribute__ 与自定义控制
虽然魔法方法本身绕过实例字典,但如果你重写了 __getattribute__,它仍会在所有属性访问(包括魔法方法名)前被调用。不过要注意:
- 在
__getattribute__内部访问任何属性(包括self.__dict__)都会再次触发它,极易引发递归错误 - 安全做法是使用
super().__getattribute__(name)或object.__getattribute__(self, name) - 即便你在这里返回一个伪造的
__len__方法,len()内置函数也不会用它——因为len绕过了__getattribute__,直取类型定义
换句话说:len(obj) 不受你自定义的 __getattribute__ 影响;但 obj.__len__() 这种显式调用会走。









