从菱形继承到 super():彻底理解 Python MRO 与多继承方法查找机制
在 Python 编程中,多继承既强大又危险。它能让我们把多个类的能力组合起来,也可能让代码变得难以理解。很多开发者第一次接触下面这种代码时,都会困惑:
python
class A:
def run(self):
print("A.run")
class B(A):
def run(self):
print("B.run")
class C(A):
def run(self):
print("C.run")
class D(B, C):
pass
d = D()
d.run()
问题来了:d.run() 到底调用 B.run,还是 C.run,还是 A.run?
答案是:
python
B.run
为什么?这就要理解 Python 的 MRO。
一、什么是 MRO?
MRO,全称是 Method Resolution Order ,中文通常翻译为 方法解析顺序。
它决定了当我们访问一个方法或属性时,Python 会按照什么顺序在类和父类中查找。
例如:
python
print(D.__mro__)
输出类似:
python
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
这说明 D 的查找顺序是:
text
D -> B -> C -> A -> object
所以当执行:
python
d.run()
Python 会先看 D 有没有 run,没有;再看 B,找到了,于是调用 B.run。
也可以使用:
python
print(D.mro())
得到同样的信息。
二、为什么 Python 需要 MRO?
在单继承中,方法查找很简单:
python
class Animal:
def speak(self):
print("animal")
class Dog(Animal):
pass
Dog().speak()
查找顺序就是:
text
Dog -> Animal -> object
但多继承会复杂得多。
python
class Flyer:
def move(self):
print("fly")
class Swimmer:
def move(self):
print("swim")
class Duck(Flyer, Swimmer):
pass
duck = Duck()
duck.move()
这里 Duck 同时继承了 Flyer 和 Swimmer。那 move() 应该来自谁?
答案取决于继承顺序:
python
class Duck(Flyer, Swimmer):
pass
MRO 是:
text
Duck -> Flyer -> Swimmer -> object
所以调用:
python
duck.move()
输出:
python
fly
如果写成:
python
class Duck(Swimmer, Flyer):
pass
则查找顺序变成:
text
Duck -> Swimmer -> Flyer -> object
输出就是:
python
swim
这说明:多继承中,父类声明顺序会影响方法查找结果。
三、菱形继承:MRO 真正发挥作用的地方
多继承最经典的问题是 菱形继承。
python
class A:
def process(self):
print("A.process")
class B(A):
def process(self):
print("B.process")
class C(A):
def process(self):
print("C.process")
class D(B, C):
pass
print(D.__mro__)
D().process()
继承关系可以表示为:
text
A
/ \
B C
\ /
D
这就是所谓的菱形继承。
在 Python 3 中,输出为:
python
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
B.process
也就是说,Python 不会简单粗暴地重复查找 A,而是通过一套算法生成稳定、合理、不重复的查找顺序。
这套算法叫 C3 线性化算法。
四、C3 线性化:Python 如何解决多继承冲突?
Python 采用 C3 线性化来计算 MRO。它遵循几个核心原则:
text
1. 子类永远优先于父类
2. 父类之间的声明顺序要被尊重
3. 每个类在 MRO 中只出现一次
4. 查找顺序必须保持一致性
以前面的代码为例:
python
class D(B, C):
pass
Python 会尊重 D(B, C) 中的顺序,所以 B 要排在 C 前面。
同时,B 和 C 都继承自 A,但 A 只能出现一次,并且要排在 B 和 C 后面。
因此最终得到:
text
D -> B -> C -> A -> object
这就是为什么 D().process() 会优先调用 B.process()。
五、MRO 不只影响方法,也影响属性
虽然 MRO 名字里有 "Method",但它不只影响方法查找,也影响类属性查找。
python
class A:
value = "A"
class B(A):
value = "B"
class C(A):
value = "C"
class D(B, C):
pass
print(D.value)
print(D().__class__.mro())
输出:
python
B
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
因为 D 自己没有 value,Python 按照 MRO 去 B 中找,找到了 B.value,于是停止查找。
这也是为什么理解 MRO 对排查属性覆盖问题非常重要。
六、super() 和 MRO 的关系
很多人误以为:
python
super()
的意思是"调用父类"。
更准确地说,super() 是:
text
按照当前类的 MRO,调用下一个类的方法
这点非常重要。
看一个例子:
python
class A:
def handle(self):
print("A.handle")
class B(A):
def handle(self):
print("B.handle")
super().handle()
class C(A):
def handle(self):
print("C.handle")
super().handle()
class D(B, C):
def handle(self):
print("D.handle")
super().handle()
d = D()
d.handle()
输出:
python
D.handle
B.handle
C.handle
A.handle
很多初学者会疑惑:B 的父类明明是 A,为什么 B 里的 super().handle() 调用了 C.handle()?
因为对于 D 的实例来说,MRO 是:
text
D -> B -> C -> A -> object
当执行到 B.handle() 里的 super() 时,它不是机械地找 B 的父类 A,而是沿着 D 的 MRO,找到 B 后面的下一个类,也就是 C。
所以:
python
super()
不是"父类",而是"下一个"。
七、协作式多继承:让每个类都有机会执行
MRO 和 super() 配合,能够实现一种非常优雅的设计方式:协作式多继承。
python
class Base:
def save(self):
print("Base.save")
class ValidateMixin(Base):
def save(self):
print("Validate data")
super().save()
class LogMixin(Base):
def save(self):
print("Write log")
super().save()
class UserService(ValidateMixin, LogMixin):
def save(self):
print("UserService.save")
super().save()
service = UserService()
service.save()
输出:
python
UserService.save
Validate data
Write log
Base.save
MRO 是:
python
print(UserService.__mro__)
结果:
text
UserService -> ValidateMixin -> LogMixin -> Base -> object
这里每个类都调用了 super().save(),所以调用链能够完整地传递下去。
这就是协作式多继承的核心:每个类只负责自己的逻辑,然后把后续流程交给 MRO 中的下一个类。
八、Mixin:多继承最常见的实战方式
在 Python 项目中,多继承最常见的使用方式不是构建复杂的父子关系,而是使用 Mixin。
Mixin 可以理解为"能力插件"。
比如:
python
class JsonMixin:
def to_json(self):
import json
return json.dumps(self.__dict__, ensure_ascii=False)
class ReprMixin:
def __repr__(self):
fields = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
return f"{self.__class__.__name__}({fields})"
class User(JsonMixin, ReprMixin):
def __init__(self, name, age):
self.name = name
self.age = age
u = User("Alice", 18)
print(u)
print(u.to_json())
输出:
python
User(name='Alice', age=18)
{"name": "Alice", "age": 18}
这里 JsonMixin 提供 JSON 序列化能力,ReprMixin 提供友好的打印能力,User 通过多继承组合这些能力。
Mixin 的设计建议:
text
Mixin 尽量小而专注
Mixin 不应该保存复杂状态
Mixin 最好不要单独实例化
Mixin 命名通常以 Mixin 结尾
多个 Mixin 之间尽量减少隐式依赖
好的 Mixin 像乐高积木,职责清晰,组合灵活。
九、实战案例:权限、日志、缓存的组合
假设我们正在开发一个后台服务,需要在查询用户数据时加入权限检查、日志记录和缓存。
可以这样设计:
python
class BaseQuery:
def query(self, user_id):
print(f"Query database for user {user_id}")
return {"id": user_id, "name": "Alice"}
class PermissionMixin:
def query(self, user_id):
print("Check permission")
return super().query(user_id)
class LogMixin:
def query(self, user_id):
print(f"Log query: user_id={user_id}")
return super().query(user_id)
class CacheMixin:
_cache = {}
def query(self, user_id):
if user_id in self._cache:
print("Hit cache")
return self._cache[user_id]
print("Miss cache")
result = super().query(user_id)
self._cache[user_id] = result
return result
class UserQueryService(PermissionMixin, LogMixin, CacheMixin, BaseQuery):
pass
service = UserQueryService()
print(service.query(1))
print("---")
print(service.query(1))
输出类似:
python
Check permission
Log query: user_id=1
Miss cache
Query database for user 1
{'id': 1, 'name': 'Alice'}
---
Check permission
Log query: user_id=1
Hit cache
{'id': 1, 'name': 'Alice'}
查看 MRO:
python
print(UserQueryService.__mro__)
结果:
text
UserQueryService
-> PermissionMixin
-> LogMixin
-> CacheMixin
-> BaseQuery
-> object
这段代码的关键点是:每个 Mixin 都调用 super().query(user_id),让调用链继续向后传递。
如果某个 Mixin 忘记调用 super(),后面的逻辑就会被截断。
十、常见错误一:把 super() 当作固定父类调用
错误理解:
python
super() == 父类
正确理解:
python
super() == MRO 中的下一个类
例如:
python
class A:
def run(self):
print("A")
class B(A):
def run(self):
print("B")
super().run()
class C(A):
def run(self):
print("C")
super().run()
class D(B, C):
pass
D().run()
输出:
python
B
C
A
这正是 MRO 的效果。
十一、常见错误二:多继承中不统一方法签名
协作式多继承要求多个类的方法签名尽量兼容。
不推荐:
python
class A:
def setup(self, config):
print("A setup")
class B:
def setup(self):
print("B setup")
class C(A, B):
pass
这种情况下,调用 super() 很容易出现参数不匹配。
更推荐使用统一签名,或者使用 *args, **kwargs:
python
class A:
def setup(self, **kwargs):
print("A setup")
super().setup(**kwargs)
class B:
def setup(self, **kwargs):
print("B setup")
super().setup(**kwargs)
class Base:
def setup(self, **kwargs):
print("Base setup")
class C(A, B, Base):
pass
C().setup(config={"debug": True})
输出:
python
A setup
B setup
Base setup
在大型框架中,这种设计非常常见。
十二、常见错误三:继承顺序写反
父类顺序会直接影响 MRO。
python
class AuthMixin:
def dispatch(self):
print("auth")
super().dispatch()
class LogMixin:
def dispatch(self):
print("log")
super().dispatch()
class View:
def dispatch(self):
print("view")
class MyView(AuthMixin, LogMixin, View):
pass
MyView().dispatch()
输出:
python
auth
log
view
如果写成:
python
class MyView(LogMixin, AuthMixin, View):
pass
输出就变成:
python
log
auth
view
这在 Web 框架中尤其重要。比如认证、限流、日志、缓存这些逻辑,顺序不同,系统行为就可能完全不同。
十三、MRO 冲突:Python 也会拒绝不合理继承
有些继承关系会让 Python 无法生成一致的 MRO。
python
class A:
pass
class B:
pass
class C(A, B):
pass
class D(B, A):
pass
class E(C, D):
pass
这段代码会报错:
python
TypeError: Cannot create a consistent method resolution order
原因是:
text
C 要求 A 在 B 前面
D 要求 B 在 A 前面
E 同时继承 C 和 D
这两个要求互相矛盾,所以 Python 无法生成一个合理的 MRO。
这也是 C3 线性化的价值:它不会在模糊和冲突中随便选择一个结果,而是直接告诉你继承设计有问题。
十四、调试 MRO 的实用方法
在项目中遇到多继承问题,可以按下面步骤排查。
1. 打印 MRO
python
print(MyClass.__mro__)
或者:
python
print(MyClass.mro())
这是最直接的方法。
2. 查看方法来自哪个类
python
print(MyClass.method)
print(MyClass.__dict__.get("method"))
如果当前类没有,再去 MRO 中逐个查找:
python
for cls in MyClass.__mro__:
if "method" in cls.__dict__:
print(cls, cls.__dict__["method"])
3. 在每个方法中打印当前类
python
class A:
def run(self):
print("A.run")
super().run()
这种方式简单粗暴,但在排查调用链时非常有效。
4. 检查是否有人忘记调用 super()
在协作式多继承中,只要中间某个类没有调用 super(),后面的类就不会被执行。
十五、最佳实践:如何安全使用多继承?
1. 优先使用组合,而不是继承
很多时候,与其写:
python
class Service(AuthMixin, CacheMixin, LogMixin):
pass
不如显式组合:
python
class Service:
def __init__(self, auth, cache, logger):
self.auth = auth
self.cache = cache
self.logger = logger
继承表达"是什么",组合表达"有什么能力"。业务系统中,组合往往更清晰。
2. 多继承适合表达横向能力
适合用 Mixin 的能力包括:
text
序列化
日志
权限校验
缓存
重试
格式转换
通用校验
不适合用多继承堆叠复杂业务身份。
3. 所有协作类都调用 super()
只要你希望调用链继续传递,就必须调用:
python
super().method()
并且保持方法签名兼容。
4. 控制继承层级
继承链太深时,MRO 会变得难以理解。一般来说,业务代码中继承层级越浅越好。
5. 写测试覆盖调用顺序
对于依赖 MRO 的代码,建议写测试明确调用顺序。
python
def test_mro_order():
assert UserQueryService.mro()[:4] == [
UserQueryService,
PermissionMixin,
LogMixin,
CacheMixin,
]
这能避免后续重构时父类顺序被无意改动。
十六、一张图总结 MRO 查找过程
当执行:
python
obj.method()
Python 大致会这样查找:
#mermaid-svg-9dWnk6ArTqbbaOVN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9dWnk6ArTqbbaOVN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9dWnk6ArTqbbaOVN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9dWnk6ArTqbbaOVN .error-icon{fill:#552222;}#mermaid-svg-9dWnk6ArTqbbaOVN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9dWnk6ArTqbbaOVN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9dWnk6ArTqbbaOVN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9dWnk6ArTqbbaOVN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9dWnk6ArTqbbaOVN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9dWnk6ArTqbbaOVN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9dWnk6ArTqbbaOVN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9dWnk6ArTqbbaOVN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9dWnk6ArTqbbaOVN .marker.cross{stroke:#333333;}#mermaid-svg-9dWnk6ArTqbbaOVN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9dWnk6ArTqbbaOVN p{margin:0;}#mermaid-svg-9dWnk6ArTqbbaOVN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9dWnk6ArTqbbaOVN .cluster-label text{fill:#333;}#mermaid-svg-9dWnk6ArTqbbaOVN .cluster-label span{color:#333;}#mermaid-svg-9dWnk6ArTqbbaOVN .cluster-label span p{background-color:transparent;}#mermaid-svg-9dWnk6ArTqbbaOVN .label text,#mermaid-svg-9dWnk6ArTqbbaOVN span{fill:#333;color:#333;}#mermaid-svg-9dWnk6ArTqbbaOVN .node rect,#mermaid-svg-9dWnk6ArTqbbaOVN .node circle,#mermaid-svg-9dWnk6ArTqbbaOVN .node ellipse,#mermaid-svg-9dWnk6ArTqbbaOVN .node polygon,#mermaid-svg-9dWnk6ArTqbbaOVN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9dWnk6ArTqbbaOVN .rough-node .label text,#mermaid-svg-9dWnk6ArTqbbaOVN .node .label text,#mermaid-svg-9dWnk6ArTqbbaOVN .image-shape .label,#mermaid-svg-9dWnk6ArTqbbaOVN .icon-shape .label{text-anchor:middle;}#mermaid-svg-9dWnk6ArTqbbaOVN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9dWnk6ArTqbbaOVN .rough-node .label,#mermaid-svg-9dWnk6ArTqbbaOVN .node .label,#mermaid-svg-9dWnk6ArTqbbaOVN .image-shape .label,#mermaid-svg-9dWnk6ArTqbbaOVN .icon-shape .label{text-align:center;}#mermaid-svg-9dWnk6ArTqbbaOVN .node.clickable{cursor:pointer;}#mermaid-svg-9dWnk6ArTqbbaOVN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9dWnk6ArTqbbaOVN .arrowheadPath{fill:#333333;}#mermaid-svg-9dWnk6ArTqbbaOVN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9dWnk6ArTqbbaOVN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9dWnk6ArTqbbaOVN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9dWnk6ArTqbbaOVN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9dWnk6ArTqbbaOVN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9dWnk6ArTqbbaOVN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9dWnk6ArTqbbaOVN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9dWnk6ArTqbbaOVN .cluster text{fill:#333;}#mermaid-svg-9dWnk6ArTqbbaOVN .cluster span{color:#333;}#mermaid-svg-9dWnk6ArTqbbaOVN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9dWnk6ArTqbbaOVN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9dWnk6ArTqbbaOVN rect.text{fill:none;stroke-width:0;}#mermaid-svg-9dWnk6ArTqbbaOVN .icon-shape,#mermaid-svg-9dWnk6ArTqbbaOVN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9dWnk6ArTqbbaOVN .icon-shape p,#mermaid-svg-9dWnk6ArTqbbaOVN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9dWnk6ArTqbbaOVN .icon-shape .label rect,#mermaid-svg-9dWnk6ArTqbbaOVN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9dWnk6ArTqbbaOVN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9dWnk6ArTqbbaOVN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9dWnk6ArTqbbaOVN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
访问 obj.method()
获取 obj 的类型 cls
读取 cls.mro
从当前类开始查找 method
当前类是否定义 method?
返回并绑定方法
进入 MRO 中下一个类
执行方法
对于:
python
class D(B, C):
pass
查找路线是:
text
D -> B -> C -> A -> object
记住这句话:
text
MRO 决定查找顺序,super() 沿着 MRO 向后调用。
十七、总结:MRO 是 Python 多继承的秩序感
Python 之所以优雅,不只是因为语法简洁,还因为它在复杂问题上有清晰的规则。
MRO 解决的正是多继承中的核心问题:
text
当多个父类都提供同名方法或属性时,Python 应该先找谁?
它通过 C3 线性化算法保证:
text
子类优先于父类
父类声明顺序被尊重
共同祖先只出现一次
冲突继承会被明确拒绝
在日常开发中,你需要牢记:
text
1. 使用 ClassName.__mro__ 或 ClassName.mro() 查看查找顺序
2. 多继承中,父类顺序会影响方法调用
3. super() 调用的是 MRO 中的下一个类,不一定是直接父类
4. Mixin 适合封装横向能力
5. 协作式多继承中,每一层都应该调用 super()
6. 复杂业务优先考虑组合,而不是滥用继承
MRO 看似是一个底层机制,但它影响着 Python 面向对象编程的方方面面。理解它以后,你会更容易读懂 Django、Flask、FastAPI、SQLAlchemy 等框架中的类设计,也能在自己的项目里写出更清晰、更稳定、更可维护的代码。
技术成长往往不是记住更多 API,而是理解那些看似隐藏、却支撑整个体系运转的底层规则。MRO 就是这样一个值得你真正掌握的 Python 核心概念。
互动问题
你在项目中遇到过多继承导致的方法调用顺序问题吗?
你更喜欢使用 Mixin 组合能力,还是倾向于使用对象组合来降低继承复杂度?
欢迎在评论区分享你的经验。一个真实的踩坑案例,往往比十条抽象规则更有价值。