wbzxz 发表于 2023-6-11 07:59:43

关于super()和mro顺序的求助

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'>)

请指点分析一下, 上面这部分代码的输出顺序为什么是这样呢?

isdkz 发表于 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方法,之后顺序就是这样产生的。

sfqxx 发表于 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'>)


给个最佳答案{:10_254:}

wbzxz 发表于 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'>)

isdkz 发表于 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方法中的内容也就不会被打印出来。

wbzxz 发表于 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顺序执行.

isdkz 发表于 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方法都会被执行。

wbzxz 发表于 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 啊, 为什么没有输出呢?

isdkz 发表于 2023-6-11 10:08:02

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

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

super() 会在 mro 顺序中把方法的调用往上传,没有super它就没法传上去了,自然就没有往后执行

wbzxz 发表于 2023-6-11 10:19:13

isdkz 发表于 2023-6-11 10:08
非常抱歉,我刚刚搞错了,你的理解是对的

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

非常感谢您的指点,向您致敬.

sfqxx 发表于 2023-6-11 12:04:15

cao

wbzxz 发表于 2023-6-11 12:30:35

sfqxx 发表于 2023-6-11 12:04
cao

也非常感谢您的解答.

sfqxx 发表于 2023-6-11 12:34:42

wbzxz 发表于 2023-6-11 12:30
也非常感谢您的解答.

{:5_109:}

110309164 发表于 2023-6-12 17:04:53

1

110309164 发表于 2023-6-12 17:09:50

1
页: [1]
查看完整版本: 关于super()和mro顺序的求助