Python多继承解析机制揭秘:C3算法用三大约束杜绝逻辑混乱

【python】你知道MRO是什么嘛?你知道多继承的顺序是怎么决定的嘛?最硬核的MRO教程! — 码农高天

码农高天

Python通过引入具备局部优先、单调性与扩展前序图约束的C3算法,在多继承拓扑产生声明冲突时直接抛出TypeError中断运行,以保障方法调用链的确定性。

AI-assisted summary of 【python】你知道MRO是什么嘛?你知道多继承的顺序是怎么决定的嘛?最硬核的MRO教程! — original source. Check the original for full context.

Source published:

Key ideas

  • Python 采用 MRO(方法解析顺序)将类及其所有父类线性化为优先级队列,并在多继承方法调用时沿该队列线性前向查找,首次命中即执行。 (0:00)
  • 自 Python 2.3 起,官方引入 Dylan 语言的 C3 算法作为 MRO 的核心解析算法,以确保局部优先顺序、单调性与扩展前序图这三大约束的一致性。 (5:00)
  • 当不同父类的局部声明顺序发生互斥死锁(如分别声明 A 先于 B 与 B 先于 A)时,Python 会在类创建时直接抛出 TypeError 报错并阻止程序运行。 (10:00)
  • 日常开发中排查多继承调用疑难无需手动推导复杂算法,可直接使用 Class.__mro__ 或 Class.mro() 查看最终确立的线性解析链。 (0:00)

Detailed notes

## Background 该视频由 YouTube 频道“码农高天”于 2023 年 10 月 22 日发布。视频简介指出,本期内容将硬核介绍 Python 中的方法解析顺序(Method Resolution Order,简称 MRO),即当一个类存在复杂的继承关系时,解释器究竟如何决定优先调用哪个父类的方法。

## TL;DR 视频从 Python 菱形继承中反直觉的方法调用现象切入,系统阐述了 MRO 的概念以及通过 `__mro__` 和 `.mro()` 获取解析链条的方法。随后,作者深入剖析了自 Python 2.3 沿用至今的 C3 算法底层机制,详解其保证一致性的三大核心属性:局部优先顺序、单调性与扩展前序图。最后,视频通过具体的代码冲突实例展示了当继承顺序相互矛盾时解释器如何抛出 TypeError 崩溃,并针对日常开发中多继承的使用与排查给出了务实建议。

## Atmosphere & style 整体氛围极具硬核极客气质与学术推演感,节奏紧凑且逻辑极其严密。主讲人通过代码实机演示与论文溯源相结合的方式,层层剥开反直觉现象背后的底层数学与拓扑逻辑。观众在观看时既能感受到拨开底层黑盒机制的智力快感,又能在面对冲突死锁与工程权衡时体会到务实笃定的编码哲学。

## Bulletin - [00:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=0s) Python 采用 MRO(方法解析顺序)将类及其所有父类线性化为优先级队列,并在多继承方法调用时沿该队列线性前向查找,首次命中即执行。 - [05:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=300s) 自 Python 2.3 起,官方引入 Dylan 语言的 C3 算法作为 MRO 的核心解析算法,以确保局部优先顺序、单调性与扩展前序图这三大约束的一致性。 - [10:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=600s) 当不同父类的局部声明顺序发生互斥死锁(如分别声明 A 先于 B 与 B 先于 A)时,Python 会在类创建时直接抛出 TypeError 报错并阻止程序运行。 - [00:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=0s) 日常开发中排查多继承调用疑难无需手动推导复杂算法,可直接使用 Class.__mro__ 或 Class.mro() 查看最终确立的线性解析链。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=0s) 在面向对象编程中,简单的直觉规则(如先声明先匹配)在菱形多继承场景下往往失效,需要系统性的算法来判定优先级。 - [00:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=0s) MRO(Method Resolution Order)是 Python 决定从哪个父类优先查找并执行方法的线性化队列,类自身始终拥有最高优先级。 - [00:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=0s) 在 Python 中可通过 Class.__mro__(返回元组)或 Class.mro()(返回列表)获取当前类确立的 MRO 顺序。 - [05:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=300s) Python 2.3 开始采用源于 1996 年 Dylan 语言的 C3 线性化算法,彻底解决了传统算法在继承拓扑上的不一致缺陷。 - [05:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=300s) C3 算法的核心优势在于同时保证了局部优先顺序(Local Precedence Order)、单调性(Monotonicity)和扩展前序图(Extended Precedence Graph)。 - [05:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=300s) 局部优先顺序要求类声明中书写靠前的父类必须排在靠后的父类之前,且该先后顺序会递归约束其所有后代派生类。 - [07:22](https://www.youtube.com/watch?v=wbVP20WlMbc&t=442s) 单调性要求派生类解析方法时必须沿用直接父类确定的决策链,禁止越权推翻直接父类而向上层祖先类优先解析。 - [10:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=600s) 扩展前序图通过两类的“最小公共子类”建立偏序约束,确保体系内混入类(Mixin)与基类的相对优先级在全局保持一致。 - [10:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=600s) 若派生类的多个父类存在互相矛盾的局部先后声明,C3 算法将无法构建拓扑序列,Python 解释器会抛出 TypeError 崩溃中断。 - [10:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=600s) 尽管多继承在编程语言设计中存在争议,但在 Python 工程中,合理结合 .mro() 检查工具并保持继承层次扁平是务实的实践方案。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=0s) 为什么需要理解方法解析顺序(MRO)

在探讨 Python 底层实现时,MRO(Method Resolution Order,方法解析顺序)是一个无法绕开的核心概念。在面向对象编程中,一个类可以继承其他类的方法或属性。在单继承的简单场景下,逻辑十分直观:如果类 `M` 继承自类 `A`,当实例化 `M` 并调用方法时,程序会自然去执行父类 `A` 中的实现。然而,一旦继承结构变得复杂,直觉往往会失效。例如让类 `B` 继承类 `A`,再让类 `M` 继承类 `B`,此时调用父类方法会输出 `B` 的结果;如果改为类 `A` 与类 `B` 相互独立,而类 `M` 同时继承 `(A, B)`,根据先声明先匹配的直觉,输出会是 `A`。

真正的困惑出现在更复杂的多继承结构中。若新增一个继承自 `A` 的类 `C`,并让 `M` 继承 `(C, B)`,调用同名方法时输出仍是 `A`;但仅仅在代码中让 `B` 也继承 `A`(即典型的菱形继承),其余完全不变,输出结果却会出人意料地变成 `B`。面对这种看似反直觉的输出,码农高天指出:“在 Python 里面一个 class 如何去从它的父类里面找应该优先使用哪个父类的函数,这个顺序就叫 MRO,也就是 Method Resolution Order。”每一个类都会将自身及其所有父类进行线性化(serialization),排成一个具有严格优先级的队列,类自身拥有最高优先级。当需要调用方法或访问属性时,解释器会严格按照该优先级队列从前向后查找。

```mermaid flowchart TD accTitle: Python多继承菱形关系示意图 accDescr: 展示类M同时继承C与B,而C与B又共同继承A的多继承结构

subgraph Base["顶层基类"] A["类 A"] end subgraph Middle["中间父类"] C["类 C"] --> A B["类 B"] --> A end subgraph Derived["派生类"] M["类 M"] --> C M --> B end ```

在 Python 中获取类的 MRO 线性化顺序有两种标准方式:直接访问类的属性 `类名.__mro__`,或者调用内置方法 `类名.mro()`。通过打印可以发现,上述菱形继承中 `M` 的 MRO 实际上是 `[M, C, B, A, object]`,这精确解释了为何最终匹配到的是 `B` 而不是 `A`,因为 `B` 的优先级排在 `A` 之前;而在 `B` 未继承 `A` 的旧版本中,其 MRO 则是 `[M, C, A, B, object]`,因此优先匹配到 `A`。

- 获取 MRO 的两种方式:`Class.__mro__` 返回元组;`Class.mro()` 返回列表。 - 继承查找机制:构建新类时首先根据继承关系推导出 MRO 线性序列,方法调用时沿着序列线性前向探测,首次命中即执行。

---

[05:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=300s) C3 线性化算法与局部优先顺序(Local Precedence Order)

很多入门教程讲完 MRO 的概念与获取方式就戛然而止,但这并未触及底层的算法逻辑。自 Python 2.3 起,Python 官方正式引入了 1996 年在 Dylan 语言中首次提出的 C3 算法作为 MRO 的核心解析算法。C3 算法之所以冠以“C3”之名,是因为它能够在三个核心属性上做到一致性(Consistent)保证:扩展前序图(Extended Precedence Graph)、局部优先顺序(Local Precedence Order)以及单调性(Monotonicity)。

在这三个性质中,相对最容易理解的是局部优先顺序(Local Precedence Order)。它的基本定义是:当一个派生类显式继承多个父类时,该类必须优先使用在类定义中书写在前面的父类的方法。例如定义 `class M(A, B): pass` 时,由于 `A` 声明在 `B` 之前,局部优先顺序严格保证优先使用 `A` 的方法;若调换声明顺序为 `class M(B, A): pass`,则必定优先使用 `B` 的方法。

C3 算法对局部优先顺序的要求更为严苛,它不仅约束当前类,还递归约束其所有后代。码农高天强调:“只要你继承了 M,那么你的 MRO 里面 B 就必须要在 A 前面,这个是 local precedence order。”换言之,任何继承了 `M` 的子类,其解析序列都不得破坏 `M` 在继承声明中所确立的局部先后关系。

- C3 算法的历史:1996 年提出,最初应用于 Dylan 语言,Python 从 2.3 版本开始将其作为标准的 MRO 计算算法。 - C3 的三个核心一致性属性:局部优先顺序(Local Precedence Order)、单调性(Monotonicity)、扩展前序图(Extended Precedence Graph)。

---

[07:22](https://www.youtube.com/watch?v=wbVP20WlMbc&t=442s) 保持继承链一致的单调性(Monotonicity)

单调性(Monotonicity)是多继承算法中较难直观理解的第二个核心性质。单调性本质上要求子类必须继承父类的方法决策,而不能越权颠覆直接父类的解析结果。具体而言,任何一个类所解析使用的方法,必须来自其直接父类(Direct Superclass)所采用的方法解析链条,不能越过直接父类擅自向更高阶的祖先类回溯。

在复杂的网状多继承中,若类 `D` 继承自 `(C, B)`,且最终确立使用的是父类 `A` 中的方法实现,那么直接继承 `D` 的派生类 `M` 在解析同名方法时,也必须沿用 `A` 的实现。如果一个算法计算出的 MRO 导致 `M` 在解析方法时跳过了 `D` 所确立的匹配结果,而优先拾取了另一个更高层级父类的实现,这便构成了对单调性的破坏。

在 C3 论文中给出的经典反例生动说明了单调性被破坏的后果: 在某种缺陷线性化算法中,定义了小艇(Day Boat)与轮船(Wheel Boat)等混合继承体系。派生类明轮船(Paddle Wheel Boat)的解析顺序中,小艇排在轮船前面;而小型双体船(Small Catamaran)仅继承小艇;但在同时继承了两者的脚踏船(Pedalo)中,算法却将轮船排在了小艇前面。此时,如果某个方法仅存在于小艇与轮船中,脚踏船的两个直接父类本都应该解析到小艇的方法,但脚踏船自身最终调用的却是轮船的方法。这种子类推翻父类决策的现象,即违背了单调性。

---

[10:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=600s) 扩展前序图(Extended Precedence Graph)与 MRO 的有效性冲突

C3 算法中最难理解的第三个属性是扩展前序图(Extended Precedence Graph,简称 EPG)。继承关系中的前序图定义了子类与父类之间的有向依赖,而扩展前序图则进一步为图中的任意两个类确立偏序关系。确立规则取决于这两个类的“最小公共子类”(Lowest Common Subclass,即该类是两者的共同子类,且该类的任何直接父类都不是两者的共同子类)。在最小公共子类中,只要类自身或其某个派生类排在另一个类(或其派生类)之前,这个前后关系就必须固化到整个系统的优先级判定中。

论文中以带滚动功能与编辑功能的面板为例进行了说明:对于 `ScrollingMixin` 与 `EditingMixin` 这两个混入类,它们的最小公共子类是同时具备滚动和编辑功能的 `EditableScrollablePane`。在该子类中,由于书写声明时的局部优先顺序,滚动面板(`ScrollablePane`)排在编辑面板(`EditablePane`)之前,而它们分别派生自对应的混入类。因此,依据扩展前序图的约束,在整个解析拓扑中,`ScrollingMixin` 的全局优先级必须先于 `EditingMixin`。

然而,并不是任意设计的多继承结构都能在满足上述三大约束的前提下计算出合法的 MRO。码农高天在代码中构造了明显的冲突案例: 若类 `C` 继承自 `(A, B)`,要求 `A` 必须排在 `B` 之前;而类 `D` 继承自 `(B, A)`,要求 `B` 必须排在 `A` 之前。此时若定义一个新类 `M` 同时继承 `(C, D)`,系统便陷入了无法解开的死锁。VS Code 等现代编辑器能直接识别语法层面的矛盾,在实际运行代码时,Python 解释器更会在编译构建类对象阶段直接抛出 `TypeError`,明确报错无法构建一致的方法解析顺序(`Cannot create a consistent method resolution order`)。

```mermaid flowchart TD accTitle: MRO冲突死锁示意图 accDescr: 展示类C要求A先于B,而类D要求B先于A,导致子类M产生继承冲突

subgraph ConflictClasses["声明顺序相反的父类"] C["class C(A, B)<br/>要求: A > B"] D["class D(B, A)<br/>要求: B > A"] end subgraph Target["目标派生类"] M["class M(C, D)"] --> C M --> D end Target -.->|"抛出 TypeError<br/>无法满足一致性"| Err["崩溃:Cannot create a consistent MRO"] ```

关于面向对象中多继承的存在意义,程序设计领域长期存在激烈争论。部分程序员持极端立场,认为多继承甚至继承本身是设计的“原罪”,因此某些现代编程语言完全摒弃了多继承,仅支持单继承或接口组合。但在 Python 等主流动态语言中,多继承仍被视为合法且强大的特性。面对复杂的算法规则,码农高天对日常开发给出了务实的建议:“你可以不用管过程,你可以不用管原理,起码你要知道怎么拿到结果。”在实际编码中,尽量保持继承关系的简洁明晰,在遇到疑难排查时善用 `.mro()` 检查实际解析队列,便足以应对绝大部分工程场景。

- 语法崩溃原因:当父类之间存在相互矛盾的局部先后声明时,C3 算法无法完成拓扑排序,直接抛出 `TypeError` 中断执行。 - 架构设计权衡:多继承极易滋生菱形继承与命名冲突,实际工程中应优先考虑扁平结构,或直接使用属性查询验证优先级。

## Notable quotes > [00:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=0s) 在 Python 里面一个 class 如何去从它的父类里面找应该优先使用哪个父类的函数,这个顺序就叫 MRO,也就是 Method Resolution Order。 — 码农高天

> [10:00](https://www.youtube.com/watch?v=wbVP20WlMbc&t=600s) 你可以不用管过程,你可以不用管原理,起码你要知道怎么拿到结果。 — 码农高天

## Mentioned Python, MRO, C3 线性化算法, Dylan, VS Code, TypeError

## Video description 这期视频介绍一下MRO,Method Resolution Order,也就是当一个类有多个继承关系的时候,是如何决定使用哪个类的方法的。内容偏硬核,请准备好大脑。

[Source](https://www.youtube.com/watch?v=wbVP20WlMbc)

中文简报

Python多继承解析机制揭秘:C3算法用三大约束杜绝逻辑混乱 Python通过引入具备局部优先、单调性与扩展前序图约束的C3算法,在多继承拓扑产生声明冲突时直接抛出TypeError中断运行,以保障方法调用链的确定性。 视频从 Python 菱形继承中反直觉的方法调用现象切入,系统阐述了 MRO 的概念以及通过 `__mro__` 和 `.mro()` 获取解析链条的方法。随后,作者深入剖析了自 Python 2.3 沿用至今的 C3 算法底层机制,详解其保证一致性的三大核心属性:局部优先顺序、单调性与扩展前序图。最后,视频通过具体的代码冲突实例展示了当继承顺序相互矛盾时解释器如何抛出 TypeError 崩溃,并针对日常开发中多继承的使用与排查给出了务实建议。 Python 采用 MRO(方法解析顺序)将类及其所有父类线性化为优先级队列,并在多继承方法调用时沿该队列线性前向查找,首次命中即执行。 (0:00) 自 Python 2.3 起,官方引入 Dylan 语言的 C3 算法作为 MRO 的核心解析算法,以确保局部优先顺序、单调性与扩展前序图这三大约束的一致性。 (5:00) 当不同父类的局部声明顺序发生互斥死锁(如分别声明 A 先于 B 与 B 先于 A)时,Python 会在类创建时直接抛出 TypeError 报错并阻止程序运行。 (10:00) 日常开发中排查多继承调用疑难无需手动推导复杂算法,可直接使用 Class.__mro__ 或 Class.mro() 查看最终确立的线性解析链。 (0:00)

Reading summary

码农高天在教程中解析了 Python 的方法解析顺序(MRO)机制,指出其本质是利用 C3 算法将继承树线性化,以保证局部优先顺序、单调性与扩展前序图这三大约束属性的一致性。 多继承下的调用困惑与 MRO 队列 (0:00) 在面向对象编程的简单单继承下,方法调用的回溯路径十分直观,但在复杂的多继承场景中,直觉往往会失效。例如在典型的菱形继承结构中,类 M 同时继承自类 C 与类 B,而 C 和 B 又共同派生自基类 A;在此结构下调用同名方法,输出结果会优先匹配 B 而非基类 A。这是因为 Python 解释器在定义类时,会将类自身及其所有父类按照特定规则排成一个具有严格优先级的线性化队列,这便是方法解析顺序(Method Resolution Order,简称 MRO)。 当程序访问属性或调用方法时,解释器会严格顺着该优先级队列由前向后进行线性探测,首次命中即执行。在 Python 中,开发者可以通过两种标准途径显式获取该线性序列:直接访问类的属性 Class.__mro__(返回元组形式),或者调用内置方法 Class.mro()(返回列表形式)。上述菱形继承中类 M 的 MRO 实际为 [M, C, B, A, object],这清晰解释了为何排在 A 前面的 B 会被优先选中。 C3 算法的核心:局部优先顺序与单调性 (5:00) 自 Python 2.3 起,官方引入了 1996 年在 Dylan 语言中提出的 C3 线性化算法。C3 之所以冠以此名,在于它能对三项核心属性做到一致性保证。相对最直观的属性是局部优先顺序(Local Precedence Order),它要求当类声明多个父类时,书写在前的父类必须拥有更高优先级。此外,该规则还具有向下的强约束力,即任何继承了该类的派生后代,其 MRO 队列中也绝不能颠覆祖先在声明中确立的先后顺序。 第二个核心属性是单调性(Monotonicity)。单调性规定子类必须沿用父类的方法决策,而不能跳过直接父类擅自向更高阶的祖先类回溯。在 C3 论文的反例中,若脚踏船同时继承的小型双体船与明轮船都决定使用小艇的方法,但在脚踏船的算法输出中却被解析到轮船的方法,便构成了子类颠覆父类解析结果的单调性破坏。C3 算法杜绝了此类异常行为,确保继承链条中的查找偏序保持全局单调递进。 扩展前序图与继承死锁冲突 (10:00) C3 算法保证的第三个属性是扩展前序图(Extended Precedence Graph,简称 EPG)。该属性通过寻找类之间的“最小公共子类”(Lowest Common Subclass),来确定更广泛继承网络中任意两个类的偏序关系,并将局部的偏序约束固化至系统的全局优先级拓扑中,例如在混入类 Pane 结构的案例中确立 ScrollingMixin 必须优先于 EditingMixin。 然而,并非所有的多继承网络都能得出合法的 MRO。如果继承关系中存在相互矛盾的先后声明——例如类 C 要求 A 先于 B,而类 D 要求 B 先于 A,当派生类 M 试图同时继承 (C, D) 时,C3 算法便会陷入无法解开的死锁。此时,Python 解释器会在构建类对象的编译阶段直接抛出 TypeError 异常,明确报告无法创建一致的方法解析顺序(Cannot create a consistent method resolution order)。 针对多继承所引发的菱形继承、命名冲突及理论争论,码农高天建议在日常工程实践中尽量保持继承层次的扁平明晰;开发者即使不深究底层推导数学过程,也应掌握利用 .mro() 检查解析队列的方法,以高效排查优先级异常。

Source quotes

[0:00] 码农高天: 在 Python 里面一个 class 如何去从它的父类里面找应该优先使用哪个父类的函数,这个顺序就叫 MRO,也就是 Method Resolution Order。 [10:00] 码农高天: 你可以不用管过程,你可以不用管原理,起码你要知道怎么拿到结果。

Original transcript

[0:00] 最近准备了若干个题材但是发现每个题材讲到底层的时候都有点绕不过去MRO为了避免在视频里面老留一个坑啊我们今天直接把MRO给讲了MRO它的全称是method resolution order或者你可以叫它方法解析顺序但是这几个词对你来说没什么意义对吧我们来大概讲一下它是个什么东西我们知道在python语言里是支持继承这个概念的也就是我这个class可以继承一些其他的class的方法或者是数据在继承的时候有的情况下是比较简单的 比如说我左边给这代码它就是一个class M继承了一个class A然后当我们建立了一个m的object 然后再用c这个函数的时候它就会printA 这个非常简单但是呢 在很多的项目里面 继承关系可能会比这个更复杂比如像这段程序它是B继承了Am又继承了b那A跟B就同时是M的鸡肋了这个时候M.C会打印出什么呢你的直觉很可能告诉你是B然后确实也是B但是你有没有想过为什么他会打B呢如果你觉得你想过并且已经想通了那我们可以看一下下面这个例子我们把计程关系改了一下 现在B不再计程A了 A跟B是两个单独的class M同时计程A和B这段代码的结果又是什么呢结果和你的直觉应该也是一样答应的是A如果你觉得这个问题依然很简单的话我们接着看现在代码右边了 我们多了一个class c 然后这个c继承aM继承C和B这次他应该打印出来什么呢结果是A如果你依然觉得这件事跟你的直觉完全相符的话 试一下接下来的代码所有的代码都没变 只是在第五行我让class B也继承了class A其他的全都没变哦这个代码结果是什么输出是B 你猜对了吗好那刚才说这么一大堆例子呢只是想告诉大家在仅仅有这么几个class的情况下不同的继承关系已经会让你稍微有点迷糊了那在python里面一个class如何去从它的负类里面找应该优先使用哪个负类的函数这个顺序就叫MRO也就是Method Resolution Order我们提到的MRO是每一个类会把它所有的负类和自己做一个线性化 serialization也就是把所有他继承的类合他自己做一个排队这个排队是保证自己是最高优先级在他想调用一个方法或者寻求一个数据的时候他会按照这个对列的优先级顺序从前往后找那么想获取一个类的MRO 也就是一个类的继承的优先级顺序有两种方式 你可以用这个类.mro或者这个类.mro括号可以看到现在class m的mro是mcBA 然后 object这也就解释了为什么这种方式它会打印B因为class B的优先级在class A前面那在上一个例子里面 如果class B没有继承class A的话M的MRO就是MCAB这个时候他就会打印A总结一下 当你建立了一个新的类 并且继承了其他类的时候 它首先会根据继承关系计算出mro然后当你尝试调用这个类的方法或者使用这个类的数据的时候他会根据MRO依次的往前找每一个在优先级列表上面的类有没有这个方法或者有没有这个数据比如我们现在的这个例子的MRO是MCAB 对吧当我们使用m.c的时候他就会先找m这个class有没有没有C这个class有没有?没有这个class有没有 有就用它了这个就是继承和mro的关系好 如果是一般的教程的话mro的部分到这会儿就结束了他会告诉你MRO是干什么用的对吧然后再告诉你MRO怎么获取然后结束了这显然非常不符合我们的视频风格 对吧所以我们还要接着往下聊Python的MRO用的是什么样的算法它可以满足什么需求这是python官网的一个 documentation,python从2.3版本开始,mro的算数 [5:00] 法就没有变过他这里提到了Python的MRO是用的C3算法C3算法是96年被提出的他最开始是被用在Dylan这个语言上这个C3线性化之所以叫C3 因为它在三个属性下都可以做到Consistent或者说是他满足三个属性第一个是他满足这个叫extended precedence graph然后第二个是它保存了local presence order最后呢就是它符合这个monetanicity那这几个词呢大家可能都不认识啊其实我一开始也不认识这个词还是有几个认识的 但是什么意思我不懂所以我特意找到了这个C3算法被提出来的那篇paper我把这篇paper简单读了一下终于搞明白了这三个C分别是什么东西那这个算法的具体内容我们就不讲了因为确实相对来说比较复杂我们只来讲一下这个算法保证了三个什么事情那在你理解了这件事之后当你再去写class的继承的时候你就会心里有个数到底我写的这个继承结构我的class会优先使用哪一个它的负类的方法而不是每一次写完之后只能mro去查好 那这三个C里面呢 第一个C相对来说最难理解 所以我们从第二个开始讲 也就是什么叫local precedence orderLocal Precidence Order就是局部的优先顺序它的意思是当你一个class继承了多个class的时候它会优先使用你写在前面的class的方法举个例子 M同时继承了A和B由于A写在前面所以根据local precedenceM一定会优先使用A的方法这个例子我们刚才已经试过了 它确实是printl A 我们现在把计程顺序换一下我们看 如果 m 先继承的 b 再继承的 a这段代码就会打印B了C30的local precedence 实际上还有一个额外的要求就是除了M要保持这个特性之外m的所有的subclass 也就是如果你有任何class继承了m他们里面也要保证这个特性就只要你继承了M那么你的MRO里面B就必须要在A前面这个是local precess order然后接下来我们讲第三条叫monetonicity也就是单调性那单调性这个概念呢 相对来说比较难理解一点我尝试讲一下 听懂听懂拉倒啊单调性指的是任何一个class他使用的方法必须来自于它的直接附类也就是Direct Superclass使用的方法我们看这是一个相对比较复杂的继承结构class D同时继承了C跟B那local precedence只要求在地里面要先搜索c再搜索b它是并没有规定A跟B的先后关系的那如果我们假设在D里面最后使用的是A的这个C那在M里面也必须使用A的这个C因为M只有一个负类是D所以D的say用的哪个M的C就要用哪个那实际上M有可能继承了不止一个D 有DEF之类的对不对由于种种的继承关系 你在算的时候 可能MRO里面B跑到了A前面但是如果M的直接负类DEF里面没有人使用B的话M使用B就是非法的他不可以跳过自己的直接负类去往上找我不知道这事说没说明白啊因为它实际上涉及到了两个概念就是这个class实际上用的是哪个方法和class之间的排序如果我没想明白的话呢我们来看一下这个文献右下角这里他给了一个继承图然后在上面他列出了当前某一种线性化的算法会算出来的结果第一个部分,Paddle wheel bolt.他的MRO里面Day Boat是在Wheel Boat前面的第二部分是small catamaran,他的MRO里面只有dayboat,没有wheelboat然而第三个Pedalow 注意一下它的MROWheelboat是在Dayboat前面的这个是当前的一种算法现金化的结果那这个结果就违反了我们刚才说的单调性的顺序因为假设有一个方法只存在于dayboat跟wheelboat上那么Padalo的两个附类都是使用的Dayboat的方法而Paddle会使用Wheelboat [10:00] 这样就被认为是违反了monetanicity了 违反了单调性了讲到这如果还听不懂那就没有办法了最后是这个最难理解的叫extended presence graphPresence Graph就像右下角这幅图一样说的是subclass跟它的superclass之间的继承关系extended presence graph指的是把这里面的每一对class中间都画上箭头画箭头的方式取决于在他们的最小公共子类上这两个类或者是他们的子类的优先级顺序这个概念是不是比刚才那个概念还难理解我们看一下右上角这幅图我们主要来观察这两个类一个叫scrolling mixing一个叫editing mixing我们最开始讲的local precedence保证什么呢它保证的是在所有scrollable pin和它的subclass里面Pain都要优先于scrolling mixing同时它保证了这个editable pain和它的subclass里面pain要优先于editing mixing我们刚才讲的单调性保证了在这个Editable Scrollable Pin里面它一定是使用的scrollable pane或者editable pane里面使用的方法比如说这个方法只有这个editing mixing 跟这个scrolling mixing 里面有那显然他用这两个类里面的哪一个都是可以的invented a direct superclass.两个class用了两个method而这个论文里面引入的EPG也就是extended presence graph讲的是scrolling mixing跟editing mixing他们两个之间应该也有一个顺序他们两个之间的顺序怎么决定呢首先要找到他们两个的最小公共子类也就是这个类是他们两个的子类并且这个类的所有负类都不是他们两个的子类叫最小公墓之类那显然在这种情况下就是最下的这个editable scrollable pin接下来就是对于他来说只要有一个scrolling mixing或者是scrolling mixing的子类排在Editing Mixing或者是Editing Mixing的子类之前那么scrolling mission就要比editing mission靠前那么在这幅图里面由于local precedencescrollable pin是要比editable pin要靠前的而它们俩分别是scrolling mixing跟editing mixing的子类所以说对于这个editable scrollable pane来说scrolling mixing就要排在editing mixing前面这个叫符合extended presence graph好 我已经非常努力了啊如果你听不懂也是正常的因为我也搞了好几个小时才搞明白这几个词到底是什么意思如果你觉得这个事情没有用的话也不是非要听懂就当天歌乐自己写计程结构的时候写的简单一点或者直接用.mro来找到它的计程顺序就可以了你可以不用管过程你可以不用管原理起码你要知道怎么拿到结果那有关MRO我们还上来最后一个问题就是我刚才说了那么多制约你可以保证你的继承图永远有满足这些制约的MRO吗答案必然是 不是 对吧我们可以写出来很多非常简单的例子 比如说C继承的是ABD继承的是BA那就要求C的子类A必须要在B前面D的子类B必须要在A前面如果我有一个class m 同时继承了 c 跟 d显然这块就是矛盾的那大家也可以看到在第15行的时候呢VSCode已经给我标出来这块是个错的东西了我们尝试运行一下python 会在 compile 这段代码时候报错啊说他没有办法建立一个 consistent method resolution order也就是说这样的继承结构本身就是不正确的你没有办法拿到一个符合直觉的MRO那有关继承这件事本身呢有一些程序员会认为多继承就是原罪就编程里面压根不应该出现多集成这种东西事实上也是有一些语言是不允许多集成 只允许单集成的当然也有一部分极端的程序员认为继承本身就是错的这个都是大家对编程的看法那现在的主流编程原理呢 支持多继承的还是多数啊包括在我们主讲的拍档里面多继承也是一个非常合法的用法还是像之前说的 首先把继承的结构尽量弄清晰其次知道MRO是什么并且学会获取计算之后的MRO对于你写python来说基本就够了至于为什么我今天要讲这么多不就是为了一个一键三连吗好 那这期视频就到这里 如果你喜欢的话记得点一下关注 别忘了意见三连哦优优独播剧场——YoYo Television Series Exclusive