鱼C论坛

 找回密码
 立即注册
查看: 2141|回复: 14

[已解决]关于super()和mro顺序的求助

[复制链接]
发表于 2023-6-11 07:59:43 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
class A:
    def say(self):
        print("FishA")

class B(A):
    def say(self):
        super().say()
        print("FishB")

class C(A):
    def say(self):
        super().say()
        print("FishC")

class D(B,C):
    def say(self):
        B.say(self)
        C.say(self)
        print('FishD')

d = D()
d.say()
print(D.__mro__)

FishA
FishC
FishB
FishA
FishC
FishD
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

请指点分析一下, 上面这部分代码的输出顺序为什么是这样呢?
最佳答案
2023-6-11 08:06:28
Python 的多继承中,一个类可以继承多个类,当多个父类中有相同的方法时,会按照一定的顺序来查找这个方法,这就是所谓的方法解析顺序(Method Resolution Order,MRO)。Python 的 MRO 是基于 C3 线性化算法的,保证所有的子类总是会先于父类被检查,有些父类又会根据其在列表中的顺序被检查。

具体到这段代码,我们可以先分析一下 MRO 顺序。当你执行  D.__mro__  时,输出的元组就是其 MRO,即  (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) 。这意味着,Python 在查找  D  类的方法时,会按照  D -> B -> C -> A -> object  的顺序去查找。

然后看  D  类中的  say  方法:
def say(self):
    B.say(self)
    C.say(self)
    print('FishD')

D.say  方法先调用了  B.say  方法,然后调用了  C.say  方法,最后打印 'FishD'。因此, B.say(self)  的执行应该先于  C.say(self) 。

在  B  类的  say  方法中,使用了  super().say() ,这会按照 MRO 顺序查找下一个类( B  的下一个类是  C ),然后在  C  中调用  say  方法。所以, B.say(self)  的结果是 'FishA' 和 'FishC'。

然后  C.say(self)  被调用,和  B.say(self)  类似,这会导致  C  类中的  say  方法被执行,然后在  C  的父类  A  中查找  say  方法并执行,因此, C.say(self)  的结果是 'FishA' 和 'FishC'。

综上,这段代码的执行顺序和结果是:
FishA
FishB
FishA
FishC
FishD
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

这是由于  super()  调用了  B  的  say  方法,然后  B  类的  say  方法又调用了  super().say() ,按照 MRO 顺序找到了  C  类的  say  方法,而  C  类的  say  方法再次调用了  super().say()  找到了  A  类的  say  方法,之后顺序就是这样产生的。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-6-11 08:06:28 | 显示全部楼层    本楼为最佳答案   
Python 的多继承中,一个类可以继承多个类,当多个父类中有相同的方法时,会按照一定的顺序来查找这个方法,这就是所谓的方法解析顺序(Method Resolution Order,MRO)。Python 的 MRO 是基于 C3 线性化算法的,保证所有的子类总是会先于父类被检查,有些父类又会根据其在列表中的顺序被检查。

具体到这段代码,我们可以先分析一下 MRO 顺序。当你执行  D.__mro__  时,输出的元组就是其 MRO,即  (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) 。这意味着,Python 在查找  D  类的方法时,会按照  D -> B -> C -> A -> object  的顺序去查找。

然后看  D  类中的  say  方法:
def say(self):
    B.say(self)
    C.say(self)
    print('FishD')

D.say  方法先调用了  B.say  方法,然后调用了  C.say  方法,最后打印 'FishD'。因此, B.say(self)  的执行应该先于  C.say(self) 。

在  B  类的  say  方法中,使用了  super().say() ,这会按照 MRO 顺序查找下一个类( B  的下一个类是  C ),然后在  C  中调用  say  方法。所以, B.say(self)  的结果是 'FishA' 和 'FishC'。

然后  C.say(self)  被调用,和  B.say(self)  类似,这会导致  C  类中的  say  方法被执行,然后在  C  的父类  A  中查找  say  方法并执行,因此, C.say(self)  的结果是 'FishA' 和 'FishC'。

综上,这段代码的执行顺序和结果是:
FishA
FishB
FishA
FishC
FishD
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

这是由于  super()  调用了  B  的  say  方法,然后  B  类的  say  方法又调用了  super().say() ,按照 MRO 顺序找到了  C  类的  say  方法,而  C  类的  say  方法再次调用了  super().say()  找到了  A  类的  say  方法,之后顺序就是这样产生的。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-6-11 08:36:12 | 显示全部楼层
上面这段代码定义了4个类: A、B、C 和 D。其中 B 和 C 继承自 A,D 继承自 B 和 C。每个类都有一个 say 方法,并且 B 和 C 中的 say 方法使用 super() 来调用父类 A 中的 say 方法。

在调用 d.say() 时,由于 D 类没有重写 say 方法,因此会沿着继承链调用 B 和 C 中的 say 方法。首先调用 B.say(self),这个方法中又调用了 super().say() 来调用 A 中的 say 方法,因此输出 FishA;接着调用 C.say(self),这个方法也调用了 super().say() 来调用 A 中的 say 方法,因此输出 FishA 和 FishC;最后输出 FishD。

由于 D 继承自 B 和 C,因此其方法解析顺序为 D -> B -> C -> A。可以通过访问 D 类的 __mro__ 属性来查看方法解析顺序。输出结果是:(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)。这个元组表示了方法解析顺序,从左到右依次为 D、B、C、A 和 object。这意味着在查找某个方法时,Python 会先查找 D,然后是 B,在 C 中找到,最后在 A 中找到。如果还没找到就会去查找 object 类了。

所以程序运行结果是:
FishA
FishB
FishA
FishC
FishD
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

给个最佳答案
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-6-11 08:51:06 | 显示全部楼层
本帖最后由 wbzxz 于 2023-6-11 08:52 编辑
isdkz 发表于 2023-6-11 08:06
Python 的多继承中,一个类可以继承多个类,当多个父类中有相同的方法时,会按照一定的顺序来查找这个方法 ...


非常感谢您的指点,再深入的问一下,下面的两段代码,分别去掉了B类中的super()和C类中的super(), 为什么都导致了FishA不能输出
print('两层的类的继承, 去掉B的supper()')
class A:
    def say(self):
        print("FishA")

class B(A):
    def say(self):
        # super().say()
        print("FishB")

class C(A):
    def say(self):
        super().say()
        print("FishC")

class D(B, C):
    def say(self):
        super().say()

d = D()
d.say()
print(D.__mro__)


FishB
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

print('两层的类的继承, 去掉C的supper()')
class A:
    def say(self):
        print("FishA")

class B(A):
    def say(self):
        super().say()
        print("FishB")

class C(A):
    def say(self):
        # super().say()
        print("FishC")

class D(B, C):
    def say(self):
        super().say()

d = D()
d.say()
print(D.__mro__)

FishC
FishB
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-6-11 08:53:12 | 显示全部楼层
wbzxz 发表于 2023-6-11 08:51
非常感谢您的指点,再深入的问一下,下面的两段代码,分别去掉了B类中的super()和C类中的super(), 为什 ...

这两个例子中, super().say()  的调用都被去掉了,这就意味着在  B  类和  C  类的  say  方法中,不再调用它们的父类( A  类)的  say  方法。当你调用  d.say()  时,Python 会按照 MRO 顺序来查找  say  方法。

在第一个例子中, B  类的  say  方法中没有  super().say() ,即没有调用  A  类的  say  方法,因此 "FishA" 没有被打印出来。MRO 顺序是  D -> B -> C -> A ,所以首先执行  B  类的  say  方法,打印出 "FishB",然后在  C  类的  say  方法中调用  super().say() ,但是由于  B  类的  say  方法已经被执行过了,根据 MRO 顺序, super().say()  会跳过  B ,尝试去执行  A  类的  say  方法,但是因为  B  类的  say  方法中并没有调用  A  类的  say  方法,所以 "FishA" 没有被打印出来。

在第二个例子中, C  类的  say  方法中没有  super().say() ,即没有调用  A  类的  say  方法,因此 "FishA" 没有被打印出来。MRO 顺序依然是  D -> B -> C -> A ,所以首先执行  B  类的  say  方法, B  类的  say  方法中的  super().say()  会调用  C  类的  say  方法,打印出 "FishC",然后再打印 "FishB",然后尝试去执行  A  类的  say  方法,但是因为  C  类的  say  方法中并没有调用  A  类的  say  方法,所以 "FishA" 没有被打印出来。

总的来说, super().say()  在这里是调用父类的  say  方法,如果在子类的  say  方法中没有调用  super().say() ,那么父类的  say  方法就不会被执行,因此父类的  say  方法中的内容也就不会被打印出来。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-6-11 09:10:15 | 显示全部楼层
本帖最后由 wbzxz 于 2023-6-11 09:12 编辑
isdkz 发表于 2023-6-11 08:53
这两个例子中, super().say()  的调用都被去掉了,这就意味着在  B  类和  C  类的  say  方法中,不再 ...


在第一个例子中, B  类的  say  方法中没有  super().say() ,即没有调用  A  类的  say  方法,因此 "FishA" 没有被打印出来。MRO 顺序是  D -> B -> C -> A ,所以首先执行  B  类的  say  方法,打印出 "FishB",然后在  C  类的  say  方法中调用  super().say() ,但是由于  B  类的  say  方法已经被执行过了,根据 MRO 顺序, super().say()  会跳过  B ,尝试去执行  A  类的  say  方法,但是因为  B  类的  say  方法中并没有调用  A  类的  say  方法,所以 "FishA" 没有被打印出来。

第一个例子是不是可以这样理解,首先执行B类的say方法,由于B中存在say方法,而且B类中的say方法没有super().say(),所以不再按照MRO顺序去执行C类中的say()方法??

第二个例子是不是可以这样理解,因为D类中有super().say()方法,所以按照MRO顺序,执行B类的super().say()方法,然后执行C类的中的say()方法,由于C类中没有super().say()方法,所以也不再按着MRO顺序去执行A类中的say()方法啦??


也就是说,碰到super()时, 按照MRO顺序执行, 如果没有见到super(), 不按照MRO顺序执行.
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-6-11 09:12:20 | 显示全部楼层
wbzxz 发表于 2023-6-11 09:10
在第一个例子中, B  类的  say  方法中没有  super().say() ,即没有调用  A  类的  say  方法,因此 "F ...

你的理解基本正确,但有一些小错误。让我详细解释一下:

对于你的第一个问题,首先执行的确实是  B  类的  say  方法,而  B  类中的  say  方法没有  super().say() ,所以它不会调用它的父类( A  类)的  say  方法。然后,因为  D  类中的  say  方法使用了  super().say() ,所以在  B  类执行之后,Python 会继续按照 MRO 顺序去找下一个类的  say  方法,也就是  C  类的  say  方法。所以,尽管  B  类中的  say  方法没有调用  super().say() ,Python 仍然会执行  C  类的  say  方法。

对于你的第二个问题, D  类的  say  方法中使用了  super().say() ,所以首先会执行  B  类的  say  方法。在  B  类的  say  方法中,也有一个  super().say()  调用,所以 Python 会按照 MRO 顺序找到并执行  C  类的  say  方法。但是  C  类的  say  方法中没有  super().say() ,所以  A  类的  say  方法不会被调用,也就不会打印 "FishA"。

在 Python 的 MRO 中,无论子类的方法是否调用了  super() ,Python 都会按照 MRO 顺序查找和执行方法,直到找不到更多的方法为止。在你的例子中,当  B  类和  C  类的  say  方法没有调用  super().say()  时,就意味着它们不会调用它们的父类  A  类的  say  方法。但是,如果有其他类(如  D  类)按照 MRO 顺序来调用  say  方法,那么  B  类和  C  类的  say  方法都会被执行。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-6-11 09:25:12 | 显示全部楼层
isdkz 发表于 2023-6-11 09:12
你的理解基本正确,但有一些小错误。让我详细解释一下:

对于你的第一个问题,首先执行的确实是  B   ...

对于你的第一个问题,首先执行的确实是  B  类的  say  方法,而  B  类中的  say  方法没有  super().say() ,所以它不会调用它的父类( A  类)的  say  方法。然后,因为  D  类中的  say  方法使用了  super().say() ,所以在  B  类执行之后,Python 会继续按照 MRO 顺序去找下一个类的  say  方法,也就是  C  类的  say  方法。所以,尽管  B  类中的  say  方法没有调用  super().say() ,Python 仍然会执行  C  类的  say  方法。

您好,这是我最糊涂的地方, 第一个例子里面, "B  类中的  say  方法没有  super().say() ,所以它不会调用它的父类( A  类)的  say  方法。", 这个我能理解
但是按照您的指点 "所以在  B  类执行之后,Python 会继续按照 MRO 顺序去找下一个类的  say  方法,也就是  C  类的  say  方法。所以,尽管  B  类中的  say  方法没有调用  super().say() ,Python 仍然会执行  C  类的  say  方法。"   执行c类 say 方法的时候, c类中是有 super(), 那c类中应该调用 A类中对应的 say() 方法, 然后输出 FishA 啊, 为什么没有输出呢?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-6-11 10:08:02 | 显示全部楼层
wbzxz 发表于 2023-6-11 09:10
在第一个例子中, B  类的  say  方法中没有  super().say() ,即没有调用  A  类的  say  方法,因此  ...

非常抱歉,我刚刚搞错了,你的理解是对的

super() 会在 mro 顺序中把方法的调用往上传,没有super它就没法传上去了,自然就没有往后执行
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-6-11 10:19:13 | 显示全部楼层
isdkz 发表于 2023-6-11 10:08
非常抱歉,我刚刚搞错了,你的理解是对的

super() 会在 mro 顺序中把方法的调用往上传,没有super它就 ...

非常感谢您的指点,向您致敬.
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-6-11 12:04:15 | 显示全部楼层
cao
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2023-6-11 12:30:35 | 显示全部楼层

也非常感谢您的解答.
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-6-11 12:34:42 | 显示全部楼层
wbzxz 发表于 2023-6-11 12:30
也非常感谢您的解答.

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-6-12 17:04:53 | 显示全部楼层
1
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-6-12 17:09:50 | 显示全部楼层
1
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-12-27 12:30

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表