Python MRO 与 super() 深度解析:多继承背后的 C3 算法

3368 字
17 分钟
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: True
can_swim: False

Swimmable.__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: True
can_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.method
B.method 结束
C.method 结束

关键点:

  1. super() 不返回父类,它返回一个代理对象
  2. 这个代理对象从 MRO 中当前类的下一个位置开始查找
  3. MRO 对所有子类是一致的——B.__mro__C 的调用栈中和单独调用时完全相同

3.3 菱形继承(Diamond Inheritance)#

菱形继承是多继承最经典的场景:

Base
/ \
Left Right
\ /
Child
class 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)

拆开理解:

  1. 子类的线性化 = 子类自身 + 合并(所有父类的线性化结果, 所有父类)
  2. merge 操作:从左到右扫描列表,选取”好头部”——出现在某个列表头部且不出现在其他列表尾部的类

merge 的具体规则:

  • 检查第一个列表的头元素 H
  • 如果 H 不在任何列表的尾部(第二个及之后的位置),H 就是好头部,输出并从所有列表中删除
  • 否则跳过这个列表,检查下一个
  • 如果所有列表的头都不是好头部,说明存在不一致的继承层次,抛出 TypeError

4.3 手动计算 MRO#

以菱形继承为例:

class A: pass
class B(A): pass
class C(A): pass
class 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: pass
class B(A): pass
try:
class C(A, B): # C 继承 A 和 B,但 B 已经是 A 的子类
pass
except TypeError as e:
print(f"TypeError: {e}")

输出:

TypeError: Cannot create a consistent method resolution order (MRO) for bases A, B

为什么?因为 MRO 必须满足两个约束:

  1. 子类优先:子类必须排在父类前面
  2. 声明顺序:父类的声明顺序必须保留

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=CA

5.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=500
LoggingMixin: log_level=DEBUG
DataProcessor: name=my-processor
log_level: DEBUG
cache_size: 500

6.2 **kwargs 参数传递模式#

关键技巧:所有混入类都接受 **kwargs 并通过 super().__init__(**kwargs) 传递

这样做的好处:

  1. 每个类只消费自己需要的参数
  2. 多余的参数自动传递给 MRO 中的下一个类
  3. 无论 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 继承自 dictMutableMapping(通过 MutableMapping 的 mixin 方法):

from collections import OrderedDict
import collections.abc
print(OrderedDict.__mro__)
# (OrderedDict, dict, object) — Python 3.7+ 中 OrderedDict 直接继承 dict

7.3 自定义元类与 MRO#

元类也参与 MRO 计算:

class MetaA(type): pass
class MetaB(type): pass
# 元类也必须满足 MRO 一致性
class CombinedMeta(MetaA, MetaB): pass
class A(metaclass=MetaA): pass
class 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继承层次矛盾重新设计继承结构
AttributeErrorMRO 中没有定义该方法检查拼写和 MRO 顺序
参数丢失混入类没传递 **kwargs所有 __init__ 都接受并传递 **kwargs

九、总结与最佳实践#

核心要点#

  1. MRO 是方法查找的路线图,按 C3 线性化算法计算,保证每个类只出现一次
  2. super() 不是”调用父类”,而是”按 MRO 查找下一个类的对应方法”
  3. 菱形继承是安全的,只要所有类都用 super() 并且调用链完整
  4. 混入类必须调用 super().__init__(**kwargs),否则链断裂
  5. C3 算法的两个硬性约束:子类优先、声明顺序保留

多继承设计原则#

  • 优先组合,次选继承:能用组合解决的问题不用继承
  • 混入类要纯粹:Mixin 只提供功能,不定义状态(或只定义独立状态)
  • 保持 MRO 短而清晰:继承深度不超过 3-4 层
  • **kwargs 传递参数:确保协作式初始化的灵活性
  • 写测试验证 MRO:对关键类的 __mro__ 写断言,防止重构破坏

延伸阅读#

掌握 MRO 和 super() 不是为了让代码充满多继承,而是为了理解那些大量使用混入类的框架(Django、pytest、SQLAlchemy)是如何工作的。当你能在心中推演出一个类的 MRO 时,阅读框架源码会像读说明书一样清晰。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Python MRO 与 super() 深度解析:多继承背后的 C3 算法
https://boke.hackerdream.xyz/posts/python-mro-super/
作者
晴天
发布于
2026-06-03
许可协议
CC BY-NC-SA 4.0
相关文章 智能推荐
1
Python functools 深度解析:lru_cache、wraps 与 partial 的底层逻辑
Python实战 深入 Python functools 标准库的核心函数,从源码层面理解 lru_cache 的 LRU 淘汰策略、wraps 的元数据拷贝机制、partial 的参数绑定原理,并掌握 reduce、cmp_to_key、singledispatch 等高级用法。
2
Python 描述符与 property:ORM 框架的核心秘密
Python实战 深入解析 Python 描述符协议(__get__、__set__、__delete__),从 property 装饰器的底层实现到 SQLAlchemy/Django ORM 的核心原理,掌握 Python 中最强大的属性拦截机制。
3
Python dataclass 进阶:post_init、field 与序列化陷阱
Python实战 深入 Python dataclass 的高级用法:从 __post_init__ 生命周期、field() 的默认值陷阱,到 dataclass 与序列化/反序列化的常见坑,掌握生产环境中 dataclass 的正确姿势。
4
Python 高级类型提示实战:Protocol、Generic 与 TypeVar 的深度用法
Python进阶 从动态类型到结构子类型,全面解析 Python typing 模块的 Protocol、Generic、TypeVar、TypeVarTuple、ParamSpec 等高级特性,附可运行代码示例与性能对比。
5
Python 弱引用与垃圾回收:缓存设计与内存泄漏排查
Python实战 深入 Python weakref 模块与垃圾回收机制,从引用计数、循环引用到分代 GC,理解弱引用字典 WeakValueDictionary、终结器 finalize、内存泄漏排查工具 gc 的实战用法,掌握缓存设计中的内存安全。
随机文章 随机推荐
Profile Image of the Author
晴天
Hello, I'm 晴天.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
155
分类
24
标签
387
总字数
345,424
运行时长
0
最后活动
0 天前

目录