Python的秘密生活:super() 与方法解析顺序

发布: (2025年12月3日 GMT+8 13:49)
5 min read
原文: Dev.to

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 搜索方法和属性的顺序:

  1. D
  2. B
  3. C
  4. A
  5. object

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()

执行流程

  1. D.process() → 打印 “D.process()”,super()B.process()
  2. B.process() → 打印 “B.process()”,super()C.process()
  3. C.process() → 打印 “C.process()”,super()A.process()
  4. 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。该算法遵循三条规则:

  1. 子类在父类之前(局部优先顺序)
  2. 从左到右的顺序(继承列表的顺序)
  3. 单调性(保留父类 MRO 中的顺序)

单调性确保:如果类 A 在任意父类的 MRO 中出现在类 B 之前,那么在子类的 MRO 中也必须保持 AB 前(除非显式覆盖)。

示例:为 D(B, C) 构建 MRO

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

C3 算法的执行过程如下:

  1. 从类本身开始
    L(D) = D + merge(L(B), L(C), [B, C])

  2. 展开父类线性化

    L(B) = B, A, object
    L(C) = C, A, object
    L(D) = D + merge([B, A, object], [C, A, object], [B, C])
  3. 合并(取第一个不出现在任何其他尾部的头部)

    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

Back to Blog

相关文章

阅读更多 »

Java OOP概念

Forem 标志https://media2.dev.to/dynamic/image/width=65,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%...

字符串比较是字典序敏感的

第78天 – 2025年12月2日 我需要下定决心,因为我在第3天和第4天的目标上仍然落后 “第3‑4天:控制结构 if‑else,loops”,以及第5天和…