我的图片 · 在线免费的图片处理工具

从一个最简单的例子出发

class Animal:
    def speak(self):
        print("动物叫")
​
class Dog(Animal):
    pass
​
d = Dog()
d.move()        # 假设写成 d.speak(),输出:动物叫

Dog 自己没写 speak,但 d.speak() 却能打印"动物叫"。

*问题来了*:Python 怎么知道去 Animal 里找的?

答案是——它手上有一张"先翻谁、后翻谁"的顺序表。这就是 MRO。

AI整理

MRO:一张固定不变的"顺序表"

*MRO*(Method Resolution Order,方法解析顺序)就是 Python 为每个类准备的一张顺序表,写明了"找方法时,按什么顺序翻"。

class Animal:
    def speak(self):
        print("动物叫")
​
class Dog(Animal):
    pass
​
print(Dog.__mro__)
# (<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>)

顺序是:Dog → Animal → object。

d.speak() 发生时,Python 就按这张表挨个翻:

*步骤**去哪翻**有没有****\*speak\*****动作*
①Dog没有(pass)继续
②Animal*有!*调用它,打印"动物叫",停

*核心认知*:MRO 是"先翻谁、后翻谁"的规则。不管那个类里有没有这个方法,它都老老实实排在表里;没有就跳过,找到了就停。

括号里的顺序决定谁在前

class Dog(Flyable, Swimmable) 里谁写前面,谁就排在 MRO 前面。

class Flyable:
    def move(self):
        print("飞")
​
class Swimmable:
    def move(self):
        print("游")
​
class Duck(Flyable, Swimmable):
    pass
​
print(Duck.__mro__)
# Duck → Flyable → Swimmable → object
​
Duck().move()    # 输出:飞   ← Flyable 排在前面,先被找到

把顺序换一下:

class Duck(Swimmable, Flyable):
    pass
​
print(Duck.__mro__)
# Duck → Swimmable → Flyable → object
​
Duck().move()    # 输出:游   ← Swimmable 排在前面了

*一句话*:括号里靠前的父类优先级高,它的方法会把后面的同名方法盖住。

父类没有这个方法?跳过,继续找

上一节两个父类都写了 move,那如果*只有一个有*呢?

class AGOOD:
    pass
​
class Animal:
    def speak(self):
        print("动物叫")
​
class Dog(AGOOD, Animal):    # AGOOD 在前,但它没有 speak
    pass
​
Dog().speak()     # 输出:动物叫
​
print(Dog.__mro__)
# Dog → AGOOD → Animal → object

查找过程:

*步骤**去哪翻**有没有****\*speak\*****动作*
①Dog没有继续
②AGOOD没有继续
③Animal*有!*调用,打印"动物叫"

AGOOD 里没有 speak,*只是被"翻了一下"就跳过了,链条不会断,也不会报错。*

父类里有另一个方法,不受影响

class Flyable:
    def fly(self):
        print("飞")
​
class Swimmable:
    def move(self):
        print("游")
​
class Dog(Flyable, Swimmable):
    pass
​
d = Dog()
d.fly()    # 输出:飞   ← 在 Flyable 找到 fly
d.move()   # 输出:游   ← 在 Swimmable 找到 move

*每次调用找的是那个具体的方法名。*Flyable 靠前,所以 fly 一定在它那里找到;查 move 时它缺席,就轮到 Swimmable。

*一句话*:父类里没有这个方法就跳过,继续查下一个。有没有方法,跟排在第几位无关。

同名方法:谁排在前面谁赢

把第 3 节和第 4 节合起来看,就是同一个机制的两种表现:

*情况**结果**为什么*
两个父类*都有*同名方法排在前面的那个赢,后面的被盖住找到就停
只有一个父类有这个方法照常找到它,前面的"空"父类被跳过没有就跳过

想让两个都执行?得用 super()

如果真想让"飞"和"游"都打印,不能只靠 MRO,得*主动把链条接下去*

class Flyable:
    def move(self):
        print("飞")
        super().move()      # 接下去,找下一个
​
class Swimmable:
    def move(self):
        print("游")
​
class Duck(Flyable, Swimmable):
    def move(self):
        print("鸭子上场")
        super().move()

输出:

鸭子上场
飞
游

*注意*:如果不写 super(),就是"找到我就结束";写了 super(),才是"我先干完,再交给下一个"。

继承关系决定 MRO 的顺序

前面几节,父类的继承关系都很简单——大家都只继承 object。*一旦父类之间也有继承关系,MRO 的顺序就不只是"括号顺序"那么简单了。*

6.1 关键对照:去掉 (SWWW) 会怎样

这是理解 MRO 最重要的一组对照。*只改****\*Swimmable\*****后面有没有****\*(SWWW)\****,其他一律不动:

# ===== 版本 A:Swimmable() 不继承 SWWW =====
class SWWW:
    def move(self):
        print("SWWW 移动")
​
class Swimmable:               # ← 没有 (SWWW)
    def move(self):
        print("游")
        super().move()
​
class Flyable(SWWW):
    def move(self):
        print("飞")
        super().move()
​
class Dog(Flyable, Swimmable):
    pass
​
print([c.__name__ for c in Dog.__mro__])
# ['Dog', 'Flyable', 'SWWW', 'Swimmable', 'object']   ← SWWW 在 Swimmable 前面!
Dog().move()
# 飞
# SWWW 移动        ← "游"没打印,Swimmable 被跳过了
# ===== 版本 B:Swimmable(SWWW) 继承 SWWW =====
class SWWW:
    def move(self):
        print("SWWW 移动")
​
class Swimmable(SWWW):         # ← 有 (SWWW)
    def move(self):
        print("游")
        super().move()
​
class Flyable(SWWW):
    def move(self):
        print("飞")
        super().move()
​
class Dog(Flyable, Swimmable):
    pass
​
print([c.__name__ for c in Dog.__mro__])
# ['Dog', 'Flyable', 'Swimmable', 'SWWW', 'object']   ← Swimmable 回到 SWWW 前面
Dog().move()
# 飞
# 游
# SWWW 移动        ← "游"打印了

*对比表*:

*版本 A:****\*Swimmable()\*****版本 B:****\*Swimmable(SWWW)\****
MRO 后半段Flyable → **SWWW → Swimmable**Flyable → **Swimmable → SWWW**
d.move() 输出飞 → SWWW 移动飞 → 游 → SWWW 移动
Swimmable.move 执行了吗❌ 被 SWWW 盖住✅
Swimmable 里的 super()死代码,没跑到执行了,调到 SWWW

*为什么会反过来?* 只因为一条规则:*子类必须排在父类前面。*

  • 版本 A:Swimmable 和 SWWW 谁都不认识,没这条约束,SWWW 紧跟 Flyable 排前面,Swimmable 被挤到后面。

  • 版本 B:Swimmable 成了 SWWW 的子类,必须排在 SWWW 前面,所以回到 SWWW 之前。

 *陷阱*:版本 A 里 Swimmable 里的 super().move() 是一行*死代码*——因为 Swimmable.move 根本没被调用,里面的代码自然一行都不会执行。你以为写了 super() 就一定会往下走,其实前提是"这个类的这个方法得先被找到"。

6.2 那括号顺序还管用吗?

管用,但它*要服从"子类在父类前面"这条硬约束*。

class Dog(Swimmable, Flyable):    # 把 Swimmable 写前面
    pass

版本 B 下,MRO 变成 Dog → Swimmable → Flyable → SWWW → object,输出变成 游 → 飞 → SWWW 移动——Swimmable 靠括号顺序排到最前。

*一句话*:继承关系决定"谁必须在谁前面",括号顺序在剩下的空间里决定"谁更靠前"。Python 把两者合起来,算出唯一合法的一张表。

super() 是一次新的查找

很多人以为 super() 就是"调用父类",这是最大的误解。

7.1 "找到就停"和"super() 接力"并不矛盾

class SWWW:
    def move(self):
        print("SWWW 移动")
​
class Swimmable(SWWW):
    def move(self):
        print("游")
        super().move()
​
class Flyable(SWWW):
    def move(self):
        print("飞")
        super().move()
​
class Dog(Flyable, Swimmable):
    def move(self):
        print("狗上场")
        super().move()

输出:

狗上场
飞
游
SWWW 移动

*为什么会全部走一遍?* 因为这里一共发生了 *4 次查找*,每次都遵守"找到就停":

*第几次**谁发起**从哪开始找**停在哪**打印*
第 1 次d.move()DogDog.move狗上场
第 2 次Dog 里的 super()FlyableFlyable.move飞
第 3 次Flyable 里的 super()SwimmableSwimmable.move游
第 4 次Swimmable 里的 super()SWWWSWWW.moveSWWW 移动
  • *"找到就停"*:说的是*每一次查找*。第 1 次在 Dog 就停了,第 2 次在 Flyable 停了……

  • *"全部走一遍"*:是因为每次停下的地方,自己又用 super()*发起了下一次查找*。

打个比方:*不是一个人沿楼梯往下走,而是每一层的人干完活,按一下电梯按钮,让电梯把活送到下一层。* 每层都停,只是每层都会再叫一次电梯。

7.2 去掉一个 super(),链条立刻断

class Flyable(SWWW):
    def move(self):
        print("飞")       # 没写 super()

输出:

狗上场
飞

第 2 次查找照样在 Flyable 停下,但这次它没写 super(),*不再发起第 3 次*。Swimmable 和 SWWW 一次都没被翻到。

*谁不写****\*super()\*****,链条就断在谁那里。* 这和是不是菱形继承、是不是多继承无关。

7.3 super() 找的是"方法名",不是"父类"

这一点最绕,看个例子:

class SWWW:
    def move(self):
        print("SWWW 移动")
​
class Swimmable(SWWW):
    def move(self):
        print("游")
​
class Flyable(SWWW):
    def fly(self):            # ← 只有 fly,没有 move
        print("飞")
        super().move()        # ← super() 写在 fly 里,找的是 move
​
class Dog(Flyable, Swimmable):
    pass
​
d = Dog()
d.fly()
# 飞
# 游

执行过程:

*步骤**做什么**说明*
①找 fly,在 Flyable 找到打印"飞"
②fly 里的 super().move()*新的一次查找,找的是****\*move\****
③从 Flyable 后面找 move在 Swimmable 找到,打印"游"

***\*super()\*****只认 MRO 里"下一个类"和"那个方法名"。* 所以这里调到的是 Swimmable.move,而不是" Flyable 的父类 SWWW 的 move"。SWWW 的 move 排在 Swimmable 后面,轮不到它。

*一句话*:super() 不是"调父类",而是"按 MRO 从下一个类开始,找那个同名方法"。

7.4 Dog 自己不写 move 会怎样

把第 7.1 节的 Dog 改成 pass:

class Dog(Flyable, Swimmable):
    pass
​
Dog().move()
# 飞
# 游
# SWWW 移动
***\*Dog\*****自己有****\*move\*******\*Dog\*****自己没****\*move\****
第 1 次查找停在哪Dog.moveFlyable.move
输出狗上场 → 飞 → 游 → SWWW 移动飞 → 游 → SWWW 移动
一共几次查找4 次3 次

*只差在"第一次停在哪儿"。* 后面的接力完全一样,因为 super() 和 MRO 没变。


菱形继承:老祖宗被压到最底下

第 6 节的结构其实就是*菱形继承*(也叫钻石继承):

           SWWW
          /    \
   Flyable      Swimmable
          \    /
           Dog

两条线汇到同一个老祖宗 SWWW 上。这是 MRO 算法真正要解决的核心难题。

8.1 没有 MRO 规则会出什么问题

如果按"深度优先"的老算法(Python 2 的经典类),查 Dog.move 的路径是:

Dog → Flyable → SWWW(找到!) → 停     ← Swimmable 被跳过了!

***\*Swimmable\*****被彻底无视*,它重写的 move 永远没机会执行。

8.2 MRO 如何解决这个问题

Python 的 MRO 算法(C3 算法)保证了一件事:

*每条继承线上的重写,都一定比老祖宗优先被查到。*

所以第 6 节的表里,Swimmable 和 Flyable 都排在 SWWW 前面,不会出现中间层被跳过的情况。

8.3 菱形继承里 super() 的真正价值:每个类只执行一次

class SWWW:
    def move(self):
        print("SWWW 移动")
​
class Swimmable(SWWW):
    def move(self):
        print("游")
        super().move()
​
class Flyable(SWWW):
    def move(self):
        print("飞")
        super().move()
​
class Dog(Flyable, Swimmable):
    def move(self):
        print("狗上场")
        super().move()

MRO:Dog → Flyable → Swimmable → SWWW → object

输出:狗上场 → 飞 → 游 → SWWW 移动。

***\*SWWW.move\*****只执行了一次*,虽然理论上它可以通过 Flyable 和 Swimmable 两条路被调到。*因为 MRO 把****\*SWWW\*****合并成一个位置*,链条只经过它一次。

这正是 super() 在菱形继承里不可替代的原因——用硬编码的 SWWW.move(self) 反而可能重复调用,而 super() 保证不重不漏。

8.4 对照:各种写法的结果

*写法**输出**说明*
全部用 super()(推荐)狗上场 → 飞 → 游 → SWWW 移动每个类一次,不重不漏
Swimmable 里硬编码 SWWW.move(self)同上,但链条断了以后 SWWW 被替换会调错人
Swimmable 忘了写 super()狗上场 → 飞 → 游SWWW 丢了
Dog(Swimmable, Flyable) 且全用 super()游 → 飞 → SWWW 移动顺序变了,但都执行

8.5 菱形继承会报错的情况

class Dog(Flyable, Swimmable, Flyable):   # 同一个类写两次
    pass
TypeError: Cannot create a consistent method resolution order (MRO) for bases Flyable, Swimmable

因为 Flyable 要在 SWWW 前面(自身继承),又要在 Swimmable 后面(第二次出现的位置),互相矛盾,排不出来。

这条报错是好事——MRO 算法在类定义时就帮你检查了继承结构是否合法。

总结:三句话 + 一张图

三句话

  1. *MRO 是一张顺序表*,在 class 定义时算好,永远不变;调用和方法里有没有 super() 都改不了它。

  2. *找方法就是按表挨个翻*:有这个方法就调用并停下,没有就跳过看下一个。

  3. ***\*super()\*****是一次新的查找*,从表里"下一个类"开始,找那个同名方法——它只认表和名字,不认"父类"。

一张图

MRO = 火车编组(印好就不变):
  [Dog] ──► [Flyable] ──► [Swimmable] ──► [SWWW] ──► [object]
​
super() = 车门:
  Dog.move ──开门──► Flyable.move ──开门──► Swimmable.move ──开门──► SWWW.move
  每层都"找到就停",只是每层都会再开一次门。
  没门(没写 super()),就停在这一层。

三个东西别混

*概念**什么时候确定**会不会变**作用*
*MRO 顺序表*class 定义那一行永远不变规定"先翻谁后翻谁"
***\*super()\****方法被调用时执行每次按表往下走一格发起下一次查找
*方法有没有被找到*查找过程中找到就停决定这次调用执行了谁

暂无评论

评论审核已启用。您的评论可能需要一段时间后才能被显示。

none
暂无评论...