Python MRO 与 super() 深度解析:多继承背后的 C3 算法
当你在 Python 中调用 super().__init__() 时,你真的知道它在做什么吗?
大多数教程告诉你:super() 就是调用父类方法。于是你在单继承场景下用得很开心,直到某天你写了一个菱形继承(Diamond Inheritance),发现 __init__ 被调用了两次,或者某个方法根本没被调用。那一刻你会意识到:super() 不是简单的”找爸爸”,而是一套精密的方法解析调度系统。
这篇文章会从最简单的单继承开始,一路深入到 C3 线性化算法的底层逻辑。读完后,你将彻底理解 Python 多继承的运作机制,并掌握写出健壮多继承代码的实用技巧。
一、从最简单的继承开始
1.1 单继承:super() 的舒适区
class Animal: def __init__(self, name): self.name = name print(f"Animal.__init__({name})")
def speak(self): return "..."
class Dog(Animal): def __init__(self, name, breed): super().__init__(name) # 调用父类 Animal.__init__ self.breed = breed print(f"Dog.__init__({name}, {breed})")
def speak(self): return f"{self.name} says: Woof!"
dog = Dog("Buddy", "Golden Retriever")print(dog.speak())输出:
Animal.__init__(Buddy)Dog.__init__(Buddy, Golden Retriever)Buddy says: Woof!在单继承中,super() 的行为确实简单:它找到当前类的直接父类,调用对应方法。但这只是冰山一角。
1.2 直接调用父类 vs super()
很多新手会这样写:
class Dog(Animal): def __init__(self, name, breed): Animal.__init__(self, name) # 直接调用父类 self.breed = breed这和 super().__init__(name) 在单继承中效果完全一样。但在多继承场景下,两者有本质区别:
| 方式 | 行为 | 多继承安全性 |
|---|---|---|
ParentClass.method(self) | 硬编码父类名 | ❌ 菱形继承会重复调用 |
super().method() | 按 MRO 查找下一个类 | ✅ 每个类只调用一次 |
二、多继承的复杂性
2.1 基本多继承
class Flyable: def __init__(self): print("Flyable.__init__") self.can_fly = True
def fly(self): return f"{self.name} is flying!"
class Swimmable: def __init__(self): print("Swimmable.__init__") self.can_swim = True
def swim(self): return f"{self.name} is swimming!"
class Duck(Flyable, Swimmable): def __init__(self, name): super().__init__() self.name = name print(f"Duck.__init__({name})")这里有一个陷阱。运行看看:
duck = Duck("唐老鸭")print(f"can_fly: {duck.can_fly}")print(f"can_swim: {hasattr(duck, 'can_swim')}") # False!输出:
Flyable.__init__Duck.__init__(唐老鸭)can_fly: Truecan_swim: FalseSwimmable.__init__ 没有被调用!因为 super().__init__() 按照 MRO 只找到了 Flyable,而 Flyable.__init__ 没有调用 super().__init__(),链断了。
修复方法:所有参与多继承的类都必须使用 super(),并且形成完整的调用链。
class Flyable: def __init__(self): super().__init__() # ← 关键:继续调用链 print("Flyable.__init__") self.can_fly = True
class Swimmable: def __init__(self): super().__init__() # ← 关键:继续调用链 print("Swimmable.__init__") self.can_swim = True
class Duck(Flyable, Swimmable): def __init__(self, name): super().__init__() self.name = name print(f"Duck.__init__({name})")
duck = Duck("唐老鸭")print(f"can_fly: {duck.can_fly}")print(f"can_swim: {duck.can_swim}")输出:
Swimmable.__init__Flyable.__init__Duck.__init__(唐老鸭)can_fly: Truecan_swim: True注意输出顺序:Swimmable 先于 Flyable 初始化。这就是 MRO 在起作用——super() 不是调用”声明顺序的第一个父类”,而是按照 MRO 列表查找”下一个”类。
三、MRO 方法解析顺序
3.1 查看 MRO
每个类都有 __mro__ 属性或 mro() 方法:
print(Duck.__mro__)# (<class '__main__.Duck'>, <class '__main__.Flyable'>,# <class '__main__.Swimmable'>, <class 'object'>)
print(Duck.mro())# 同上,返回列表形式MRO 是一个元组,定义了方法查找的优先级顺序。当调用 obj.method() 时,Python 按 MRO 从左到右查找,找到第一个定义了 method 的类并执行。
3.2 super() 的真实行为
super() 最常见的误解是”调用父类”。准确的说法是:super() 返回一个代理对象,它会按照 MRO 中当前类的下一个位置开始查找方法。
用代码解释:
class A: def method(self): print("A.method")
class B(A): def method(self): print("B.method 开始") print(f"B 的 MRO: {[c.__name__ for c in B.__mro__]}") super().method() print("B.method 结束")
class C(B): def method(self): print("C.method 开始") print(f"C 的 MRO: {[c.__name__ for c in C.__mro__]}") super().method() print("C.method 结束")
C().method()输出:
C.method 开始C 的 MRO: ['C', 'B', 'A', 'object']B.method 开始B 的 MRO: ['C', 'B', 'A', 'object']A.methodB.method 结束C.method 结束关键点:
super()不返回父类,它返回一个代理对象- 这个代理对象从 MRO 中当前类的下一个位置开始查找
- MRO 对所有子类是一致的——
B.__mro__在C的调用栈中和单独调用时完全相同
3.3 菱形继承(Diamond Inheritance)
菱形继承是多继承最经典的场景:
Base / \ Left Right \ / Childclass Base: def __init__(self): super().__init__() print("Base.__init__")
class Left(Base): def __init__(self): super().__init__() print("Left.__init__")
class Right(Base): def __init__(self): super().__init__() print("Right.__init__")
class Child(Left, Right): def __init__(self): super().__init__() print("Child.__init__")
print("Child 的 MRO:", [c.__name__ for c in Child.__mro__])print("---")child = Child()输出:
Child 的 MRO: ['Child', 'Left', 'Right', 'Base', 'object']---Base.__init__Right.__init__Left.__init__Child.__init__注意 Base.__init__ 只被调用了一次!如果不用 super() 而是硬编码 Base.__init__(self),Base 的初始化会执行两次——这就是菱形继承的”重复初始化”问题。
MRO 的巧妙之处在于:它保证每个类在继承链中只出现一次,即使它被多个路径继承。
四、C3 线性化算法
4.1 为什么需要 C3?
Python 2.3 之前使用”深度优先、从左到右”的 MRO 算法,这在菱形继承中会出问题:
A / \ B C \ / D旧算法:D → B → A → C → A(A 出现两次!)
C3 算法:D → B → C → A(A 只出现一次)
Python 2.3 引入 C3 线性化算法,解决了这个问题。
4.2 C3 算法的核心思想
C3 线性化是一个递归算法,公式如下:
L[C(B1...Bn)] = C + merge(L[B1]...L[Bn], B1...Bn)拆开理解:
- 子类的线性化 = 子类自身 + 合并(所有父类的线性化结果, 所有父类)
- merge 操作:从左到右扫描列表,选取”好头部”——出现在某个列表头部且不出现在其他列表尾部的类
用 merge 的具体规则:
- 检查第一个列表的头元素 H
- 如果 H 不在任何列表的尾部(第二个及之后的位置),H 就是好头部,输出并从所有列表中删除
- 否则跳过这个列表,检查下一个
- 如果所有列表的头都不是好头部,说明存在不一致的继承层次,抛出
TypeError
4.3 手动计算 MRO
以菱形继承为例:
class A: passclass B(A): passclass C(A): passclass D(B, C): pass计算过程:
L[A] = [A, object]L[B] = B + merge(L[A], A) = B + merge([A, object], [A]) = B + A + merge([object]) = [B, A, object]
L[C] = C + merge(L[A], A) = [C, A, object] (同上)
L[D] = D + merge(L[B], L[C], B, C) = D + merge([B, A, object], [C, A, object], [B], [C])
→ B 是好头部(B 不在 [A, object] 或 [A, object] 或 [C] 的尾部) = D + B + merge([A, object], [C, A, object], [C])
→ A 不是好头部(A 在 [C, A, object] 的尾部),跳过 → C 是好头部 = D + B + C + merge([A, object], [A, object])
→ A 是好头部 = D + B + C + A + merge([object], [object])
→ object = [D, B, C, A, object]验证:
print(D.__mro__) # (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)4.4 C3 不一致的情况
当继承层次导致 C3 无法找到”好头部”时,Python 会拒绝创建类:
class A: passclass B(A): pass
try: class C(A, B): # C 继承 A 和 B,但 B 已经是 A 的子类 passexcept TypeError as e: print(f"TypeError: {e}")输出:
TypeError: Cannot create a consistent method resolution order (MRO) for bases A, B为什么?因为 MRO 必须满足两个约束:
- 子类优先:子类必须排在父类前面
- 声明顺序:父类的声明顺序必须保留
C(A, B) 要求:C > A > … 且 C > B > … 且 A 在 B 前面(声明顺序)
但 B(A) 已经要求:B > A
这两个约束矛盾,C3 无法线性化。
五、super() 的高级用法
5.1 指定类和实例的 super()
super() 有两种完整的调用形式:
super() # 等价于 super(__class__, self),最常用super(CurrentClass, instance) # 指定查找起点和目标实例super(CurrentClass, subclass) # 指定查找起点和目标类(类方法中)第二种的用途:跳过 MRO 中的某些类。
class A: def greet(self): return "A"
class B(A): def greet(self): return "B" + super().greet()
class C(A): def greet(self): return "C" + super().greet()
class D(B, C): def greet(self): # 正常 super() 按 MRO [D, B, C, A] 查找 normal = super().greet() # → B → C → A # 跳过 B,直接从 C 开始 skip_b = super(B, self).greet() # → C → A return f"normal={normal}, skip_b={skip_b}"
print(D.__mro__) # [D, B, C, A, object]print(D().greet())# normal=BCA, skip_b=CA5.2 零参数的 super() 魔法
Python 3 的零参数 super() 能自动推断 __class__,这依赖编译器的魔法:
import dis
class Demo: def test(self): return super()
print(dis.dis(Demo.test))编译时,Python 会在字节码中隐式注入 __class__ 闭包变量。这也是为什么 super() 只能在方法内部使用——它需要这个隐式的 __class__ 引用。
5.3 super() 返回的是一个代理
class Base: x = 10
class Derived(Base): x = 20
d = Derived()s = super(Derived, d)
print(d.x) # 20 - 从 Derived 获取print(s.x) # 10 - 从 MRO 中 Derived 的下一个类(Base)获取super() 返回的对象不是 Base 实例,而是一个 super 对象,它拦截属性访问并按 MRO 转发。
六、实战:构建协作式多继承框架
6.1 协作式 __init__ 的正确写法
编写可安全参与多继承的类,需要遵循以下模式:
class Plugin: """基类插件,所有插件的祖先""" def __init__(self, **kwargs): super().__init__(**kwargs) self.plugins = [] print(f"Plugin 初始化完成")
class LoggingMixin: """日志混入类""" def __init__(self, log_level="INFO", **kwargs): super().__init__(**kwargs) self.log_level = log_level print(f"LoggingMixin: log_level={log_level}")
def log(self, message): print(f"[{self.log_level}] {message}")
class CacheMixin: """缓存混入类""" def __init__(self, cache_size=100, **kwargs): super().__init__(**kwargs) self.cache_size = cache_size self._cache = {} print(f"CacheMixin: cache_size={cache_size}")
def get_cached(self, key): return self._cache.get(key)
def set_cached(self, key, value): self._cache[key] = value
class DataProcessor(Plugin, LoggingMixin, CacheMixin): def __init__(self, name, **kwargs): super().__init__(**kwargs) self.name = name print(f"DataProcessor: name={name}")
def process(self, data): self.log(f"Processing {len(data)} items") # ... 处理逻辑 self.set_cached("last_result", data) return data
# 使用processor = DataProcessor( name="my-processor", log_level="DEBUG", cache_size=500)print(f"log_level: {processor.log_level}")print(f"cache_size: {processor.cache_size}")输出:
Plugin 初始化完成CacheMixin: cache_size=500LoggingMixin: log_level=DEBUGDataProcessor: name=my-processorlog_level: DEBUGcache_size: 5006.2 **kwargs 参数传递模式
关键技巧:所有混入类都接受 **kwargs 并通过 super().__init__(**kwargs) 传递。
这样做的好处:
- 每个类只消费自己需要的参数
- 多余的参数自动传递给 MRO 中的下一个类
- 无论 MRO 如何变化,所有
__init__都能被正确调用
6.3 避免的陷阱
class BadMixin: def __init__(self, **kwargs): # ❌ 错误:不继续调用链 self.bad = True # super().__init__(**kwargs) 被遗漏!
class GoodMixin: def __init__(self, **kwargs): # ✅ 正确:继续调用链 super().__init__(**kwargs) self.good = True黄金法则:如果你写了一个混入类(Mixin),并且它定义了 __init__,必须在最后调用 super().__init__(**kwargs)。
七、MRO 在真实框架中的应用
7.1 Django 的 CBV(类视图)
Django 的类视图大量使用混入类和多继承:
# Django 内部模式示意class View: def __init__(self, **kwargs): super().__init__(**kwargs)
class TemplateResponseMixin: template_name = "default.html" def render_to_response(self, context): # 渲染模板 pass
class SingleObjectMixin: model = None def get_object(self): # 获取单个对象 pass
class DetailView(SingleObjectMixin, TemplateResponseMixin, View): def get(self, request, *args, **kwargs): self.object = self.get_object() return self.render_to_response({"object": self.object})MRO: DetailView → SingleObjectMixin → TemplateResponseMixin → View → object
7.2 Python 标准库中的 collections.OrderedDict
OrderedDict 继承自 dict 和 MutableMapping(通过 MutableMapping 的 mixin 方法):
from collections import OrderedDictimport collections.abc
print(OrderedDict.__mro__)# (OrderedDict, dict, object) — Python 3.7+ 中 OrderedDict 直接继承 dict7.3 自定义元类与 MRO
元类也参与 MRO 计算:
class MetaA(type): passclass MetaB(type): pass
# 元类也必须满足 MRO 一致性class CombinedMeta(MetaA, MetaB): pass
class A(metaclass=MetaA): passclass B(metaclass=MetaB): pass
class C(A, B): # C 的元类必须是 CombinedMeta 的兼容子类 pass八、性能与调试
8.1 MRO 缓存
Python 在类创建时计算 MRO,之后缓存结果。方法查找时不会重新计算 MRO,因此多继承的运行时代价很小。
import timeit
class A: def method(self): pass
class B(A): def method(self): super().method()
class C(B): def method(self): super().method()
class D(C): def method(self): super().method()
# super() 调用开销t = timeit.timeit("D().method()", globals=globals(), number=100000)print(f"10万次 super() 链调用: {t:.4f}s")# 约 0.03-0.05s,每次调用约 300-500 纳秒8.2 调试 MRO 问题
当你遇到方法调用不按预期执行时,按以下步骤排查:
def debug_mro(cls): """打印完整的 MRO 链和每个类的方法定义""" print(f"=== MRO for {cls.__name__} ===") for i, klass in enumerate(cls.__mro__): methods = [m for m in dir(klass) if not m.startswith('_') and callable(getattr(klass, m, None))] print(f" {i}. {klass.__name__}: {methods[:5]}...")
class A: def process(self): pass
class B(A): def process(self): super().process()
class C(A): def process(self): super().process()
class D(B, C): def process(self): super().process()
debug_mro(D)输出:
=== MRO for D === 0. D: ['process']... 1. B: ['process']... 2. C: ['process']... 3. A: ['process']... 4. object: [...]...8.3 常见错误速查表
| 错误 | 原因 | 修复 |
|---|---|---|
__init__ 被调用多次 | 直接调用父类 Parent.__init__(self) | 改用 super().__init__() |
__init__ 没有被调用 | 链中某个类没调用 super() | 所有类都调用 super() |
TypeError: MRO | 继承层次矛盾 | 重新设计继承结构 |
AttributeError | MRO 中没有定义该方法 | 检查拼写和 MRO 顺序 |
| 参数丢失 | 混入类没传递 **kwargs | 所有 __init__ 都接受并传递 **kwargs |
九、总结与最佳实践
核心要点
- MRO 是方法查找的路线图,按 C3 线性化算法计算,保证每个类只出现一次
super()不是”调用父类”,而是”按 MRO 查找下一个类的对应方法”- 菱形继承是安全的,只要所有类都用
super()并且调用链完整 - 混入类必须调用
super().__init__(**kwargs),否则链断裂 - C3 算法的两个硬性约束:子类优先、声明顺序保留
多继承设计原则
- 优先组合,次选继承:能用组合解决的问题不用继承
- 混入类要纯粹:Mixin 只提供功能,不定义状态(或只定义独立状态)
- 保持 MRO 短而清晰:继承深度不超过 3-4 层
- 用
**kwargs传递参数:确保协作式初始化的灵活性 - 写测试验证 MRO:对关键类的
__mro__写断言,防止重构破坏
延伸阅读
- Python 官方文档:The Python 2.3 Method Resolution Order — C3 算法的原始论文
- Raymond Hettinger 演讲:Super considered super! — Python 核心开发者对
super()的深度解读 - Python
super()源码:Objects/typeobject.c — 看看 C 层面如何实现 - 本博客相关文章:Python 描述符与 property、Python dataclass 进阶、Python functools 深度解析
掌握 MRO 和 super() 不是为了让代码充满多继承,而是为了理解那些大量使用混入类的框架(Django、pytest、SQLAlchemy)是如何工作的。当你能在心中推演出一个类的 MRO 时,阅读框架源码会像读说明书一样清晰。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!