Python的秘密生活:super() 与方法解析顺序
Source: Dev.to
为什么 super() 并不意味着“父类”(以及它真正的作用)
Timothy 盯着屏幕,满脸困惑。他写了一段看似直接的继承代码,但输出却毫无意义。
class A:
def process(self):
print("A.process()")
class B(A):
def process(self):
print("B.process()")
super().process()
class C(A):
def process(self):
print("C.process()")
super().process()
class D(B, C):
def process(self):
print("D.process()")
super().process()
d = D()
d.process()
他本以为输出会是:
D.process()
B.process()
A.process()
毕竟,D 继承自 B,而 B 继承自 A。这就是继承的工作方式,对吧?
但当他运行代码时,得到的是:
D.process()
B.process()
C.process()
A.process()
方法解析顺序(MRO)
“
super()并不会调用你的父类,Timothy。它调用的是*方法解析顺序(Method Resolution Order)*中的下一个类,” Margaret 解释道。
Python 中每个类都有一个名为 __mro__ 的属性——方法解析顺序。它是一个元组,定义了 Python 在查找属性时遵循的精确顺序。
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
print(D.__mro__)
输出
(, , ,
, )
这个元组就是 Python 搜索方法和属性的顺序:
DBCAobject
super() 的含义是“MRO 中的下一个类”,而不是“我的父类”。
MRO 同样影响 isinstance() 和 issubclass():
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
obj = D()
print(isinstance(obj, D)) # True
print(isinstance(obj, B)) # True
print(isinstance(obj, C)) # True
print(isinstance(obj, A)) # True
print(isinstance(obj, object)) # True
print(issubclass(D, B)) # True
print(issubclass(D, C)) # True
print(issubclass(D, A)) # True
print(D.__mro__) # 所有 isinstance 返回 True 的类
追踪最初的示例
class A:
def process(self):
print("A.process()")
class B(A):
def process(self):
print("B.process()")
super().process() # B 之后的下一个类
class C(A):
def process(self):
print("C.process()")
super().process() # C 之后的下一个类
class D(B, C):
def process(self):
print("D.process()")
super().process() # D 之后的下一个类
print(D.__mro__) # (, , , , )
d = D()
d.process()
执行流程
D.process()→ 打印 “D.process()”,super()→B.process()B.process()→ 打印 “B.process()”,super()→C.process()C.process()→ 打印 “C.process()”,super()→A.process()A.process()→ 打印 “A.process()”
关键在于:B 中的 super() 并没有查看 B 的父类(A),而是查看 实例(D)的 MRO 中的下一个类,即 C。
不同实例,不同路径
# 直接实例化 B
b = B()
print("MRO of B:", B.__mro__) # (, , )
b.process()
# 输出:
# B.process()
# A.process()
# 实例化 D
d = D()
print("\nMRO of D:", D.__mro__) # (, , , , )
d.process()
# 输出:
# D.process()
# B.process()
# C.process()
# A.process()
当你调用 b.process() 时,MRO 为 B → A → object,所以 B 中的 super() 调用 A。而调用 d.process() 时,MRO 为 D → B → C → A → object,于是 B 中的 super() 调用 C。
Python 如何构建 MRO:C3 线性化
Python 使用 C3 线性化(也称为 C3 超类线性化)来确定 MRO。该算法遵循三条规则:
- 子类在父类之前(局部优先顺序)
- 从左到右的顺序(继承列表的顺序)
- 单调性(保留父类 MRO 中的顺序)
单调性确保:如果类 A 在任意父类的 MRO 中出现在类 B 之前,那么在子类的 MRO 中也必须保持 A 在 B 前(除非显式覆盖)。
示例:为 D(B, C) 构建 MRO
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
C3 算法的执行过程如下:
-
从类本身开始
L(D) = D + merge(L(B), L(C), [B, C]) -
展开父类线性化
L(B) = B, A, object L(C) = C, A, object L(D) = D + merge([B, A, object], [C, A, object], [B, C]) -
合并(取第一个不出现在任何其他尾部的头部)
merge([B, A, object], [C, A, object], [B, C]): heads → B, C, B B 不在任何尾部 → 取 B D, B + merge([A, object], [C, A, object], [C]): heads → A, C, C A 在尾部 → 跳过 C 不在任何尾部 → 取 C D, B, C + merge([A, object], [A, object]): heads → A, A A 不在任何尾部 → 取 A D, B, C, A + merge([object], [object]): heads → object, object object 不在任何尾部 → 取 object 最终 MRO: D, B, C, A, object
因此,Python 计算得到的 MRO 为 (, , , , ),这就解释了为什么在实例为 D 时,B 中的 super() 会调用 C。