news 2026/10/9 23:58:17

Python中类的mro与继承关系详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python中类的mro与继承关系详解

前言


MRO 是 Method Resolution Order 的缩写,中文常译作"方法解析顺序"。它回答一个问题:当一个实例调用某个方法时,Python 按什么顺序去各个类里找它?


单继承时这个问题看起来没什么可讲的——子类没有就往上找父类,一条直线。但 Python 支持多重继承,一个类可能有两条甚至更多路径通向同一个祖先,这时"往上找"就不再是一条直线了。文档把这种结构称为菱形关系(diamond relationship):因为所有类最终都继承object,任何多重继承都天然构成菱形。


Python 的解法是C3 线性化:把一个类及其所有祖先排成一个不重复的线性序列。围绕它常见的误解有两个:



  • 以为多重继承的方法是"深度优先、从左到右"地全跑一遍。实际每个类只会被选中一次,super()靠这个线性序列接力。

  • 以为 MRO 只看继承列表的书写顺序。实际上它还受每个基类自身的继承结构影响,光看class D(B, C)猜不准。


本文讲 C3 的规则、怎么查看 MRO,以及它和super()的关系。


一、MRO 要解决什么


假设有这样一组类:


A
/ \
B C
\ /
D

B和C都继承A,D又同时继承B和C。如果按"深度优先、从左到右"去找方法:先找D,再找B,再找B的父类A——可这时C还没被找过,而A已经被找了两遍。谁先谁后、会不会重复,就说不清了。


文档对 Python 采用的算法的描述是:它保持每个类声明时的从左到右顺序、每个父类只被访问一次、并且是单调的(即一个类被继承后不会打乱已有父类的优先级)。这套规则让多重继承的行为变得可预测。


二、C3 线性化的结果长什么样


先看最经典的菱形:


# 适用于 Python 3.8+
class A:
pass


class B(A):
pass


class C(A):
pass


class D(B, C):
pass


print([cls.__name__ for cls in D.__mro__])
# ['D', 'B', 'C', 'A', 'object']

顺序是D → B → C → A → object。注意两点:



  • B和C谁在前面,由class D(B, C)的书写顺序决定;

  • A排在两者之后,而不是紧跟B——这正是线性化避免重复访问的手段。


下表用"查找某个方法时依次去哪些类找"的角度,列出几种继承结构的 MRO:




类定义MRO



class B(A)B, A, object

class C(A)C, A, object

class D(B, C)D, B, C, A, object

class E(C, B)E, C, B, A, object

class F(D, E)冲突,抛TypeError



最后一行值得说明:D要求B在C前,E要求C在B前,两者矛盾,C3 无法给出一个一致序列,于是 Python 直接报TypeError: Cannot create a consistent method resolution order (MRO)。


三、查看 MRO:__mro__ 与 mro()


每个类都有__mro__属性,它是一个元组,列出方法解析时会依次检查的类:


# 适用于 Python 3.8+
class A:
pass


class B(A):
pass


print(B.__mro__)
# (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

类上还有mro()方法,返回的是列表形式。文档说明mro()可以被元类重写,用于自定义 MRO,它在类创建时被调用,结果存进__mro__。


# 适用于 Python 3.8+
print(B.mro())
# [<class '__main__.B'>, <class '__main__.A'>, <class 'object'>]

排查继承问题时,第一件事就是打印类名.__mro__——它比任何推测都可靠。另外__bases__给出的是直接基类的元组,__mro__才是完整链条,两者别混用。


四、MRO 与 super() 的配合


这是 MRO 最实际的用途。super()返回一个代理对象,从 MRO 中当前类之后的位置继续搜索。文档举的例子是:如果__mro__是D -> B -> C -> A -> object而type是B,那么super()会在C -> A -> object里找。


# 适用于 Python 3.8+
class Base:
def handle(self):
return ["Base"]


class AuthMixin:
def handle(self):
return ["Auth"] + super().handle()


class LogMixin:
def handle(self):
return ["Log"] + super().handle()


class API(AuthMixin, LogMixin, Base):
pass


print([c.__name__ for c in API.__mro__])
# ['API', 'AuthMixin', 'LogMixin', 'Base', 'object']
print(API().handle())
# ['Auth', 'Log', 'Base']

AuthMixin.handle里的super()跳到了LogMixin——这是它的MRO 后继,不是它的"父类"(AuthMixin的父类其实是object)。这里就体现出 MRO 与super()是一体的:不知道 MRO,就预测不出super()会跳到哪。


如果某个环节忘了写super(),它后面的所有实现都会被静默跳过,程序不报错:


# 适用于 Python 3.8+
class LogMixin:
def handle(self):
return ["Log"] # 没调 super(),Base 永远轮不到

五、MRO 冲突怎么排查


出现Cannot create a consistent method resolution order (MRO)时,说明继承结构本身矛盾,典型是"把祖先排在了后代后面":


# 适用于 Python 3.8+
class A:
pass


class B(A):
pass


# class C(A, B): # TypeError: Cannot create a consistent MRO
# pass

B已经是A的子类,class C(A, B)却要求A排在B前面,这与A必须在B之后相矛盾。


排查步骤建议固定成三条:



  1. 写出每个涉及类的__bases__,画成图;

  2. 从上到下检查有没有"祖先排在后代之后"的写法;

  3. 试着删掉某个基类,看是否是因为它把两条无关的链条强行合并导致矛盾。


常见坑点


1. 以为super()指的就是直接父类


❌ 在多重继承里断言AuthMixin.handle里的super()会调到自己的父类。


✅ 它调到的是当前实例的 MRO 中当前类之后的下一个类,要打印__mro__才能确定。


2. 用"深度优先"预测顺序


❌ 以为D(B, C)会先把B的整条祖先链找完再找C。


✅ C3 的输出是D, B, C, A, object——A被排到C之后。


3. 在嵌套函数 / 生成器表达式里用零参super()


❌ 以为super()在哪都能自动认出当前类和self,于是在方法内部又套一层函数时照写super()。


✅ 零参super()靠编译器在外围方法里填入当前类与第一个参数,嵌套函数里不成立(生成器表达式也会隐式生成嵌套函数);这种场合要写显式两参形式super(当前类, self)。


4. 以为继承列表相同,MRO 就相同


❌ 看到两个类都写成(X, Y),就断定它们的解析顺序完全一致。


✅ 顺序还取决于X、Y各自继承了谁;上游结构一变,下游顺序可能跟着变,必须打印__mro__才算数。


5. 用__bases__当完整链条


❌ 打印D.__bases__只看到(B, C),就以为这是全部顺序。


✅__bases__是直接基类;完整顺序看__mro__。


6. MRO 冲突时反复调换顺序试错


❌ 遇到TypeError就把继承列表换来换去,直到不报错。


✅ 冲突说明结构矛盾(祖先排在了后代之后),需要重新设计层次。


7. 在子类里给__mro__赋值


❌ 运行中直接SomeClass.__mro__ = ...想强行改顺序。


✅__mro__是只读的;要自定义解析顺序只能在元类里重写mro(),且在类创建前生效。


8. 忘了每个类的 MRO 末尾都有object


❌ 统计链长时漏算object,或以为重写object的方法能救场。


✅ 所有类的 MRO 都以object结尾,多重继承里它只出现一次,这也是"任意多重继承都是菱形"的原因。


总结




概念说明



MRO属性/方法查找的顺序

算法C3 线性化,保持从左到右、每个类一次、单调

查看方式类.__mro__(元组)、类.mro()(列表)

super()从 MRO 中当前类之后继续搜索

冲突表现TypeError: Cannot create a consistent MRO



MRO 不是一条只供阅读的规则,它是super()行为的依据。想预测一段多重继承代码会调到哪个方法,唯一可靠的办法就是打印__mro__,再顺着序列看每个类的实现是不是都调了super()。把这两步养成习惯,多重继承就不再是黑箱。




版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 23:57:21

Java工程师必学:J2SE核心基础与实战避坑指南

1. 为什么每个Java工程师都绕不开J2SE这个地基刚入行那会儿&#xff0c;我也觉得J2SE就是本厚得能砸核桃的说明书&#xff0c;翻两页就犯困。直到第一次面试被问到“HashMap的hashCode和equals到底怎么配合工作”&#xff0c;我才意识到&#xff0c;那些看似枯燥的基础&#xf…

作者头像 李华
网站建设 2026/10/9 23:55:44

抖店开通与运营全流程指南:资质合规、内容驱动、闭环成交

1. 抖店不是“开个网店”那么简单&#xff1a;先搞清它到底是什么、适合谁、能解决什么问题抖店不是淘宝或拼多多的翻版&#xff0c;更不是把商品拍照上传就完事的简易工具。它是抖音生态内嵌的、深度绑定内容流量的交易闭环系统——简单说&#xff0c;它把“刷到一个视频→被种…

作者头像 李华
网站建设 2026/10/9 23:52:08

Flink+HBase电商实时链路:亿级QPS下的状态管理与低延迟写查实践

简介&#xff1a;本资源是一份聚焦实时大数据架构落地的深度技术文档&#xff0c;面向大数据开发工程师、实时计算方向从业者及Flink/HBase进阶学习者&#xff0c;解决高并发、低延迟电商场景下实时数据处理与存储协同难题。文档系统解析阿里巴巴电商业务中Flink流式计算与HBas…

作者头像 李华
网站建设 2026/10/9 23:44:48

Java Future.get超时与cancel方法实战:避免线程池雪崩

1. 从一次线上事故说起&#xff1a;为什么get超时和cancel总被忽略很多写过Java并发的人都有过这种经历&#xff1a;代码里用线程池提交任务&#xff0c;调用Future.get()拿结果&#xff0c;本地测试一切正常&#xff0c;上线后某个下游接口偶尔抽风&#xff0c;整个线程池被拖…

作者头像 李华
网站建设 2026/10/9 23:30:26

奇迹MU剑与翼高效挂机全攻略:从下载到收益优化

1. 官方下载渠道全梳理&#xff1a;版本流派的辨别与选服建议1.1 认准官方渠道&#xff1a;避免下载到套壳私服先说最容易被坑的地方。市面上挂“奇迹MU剑与翼”名头的下载渠道非常多&#xff0c;搜索引擎一搜&#xff0c;前排的推广链接里鱼龙混杂&#xff0c;里面既有正经的官…

作者头像 李华