Python MRO-在多个继承中使用super

时间:2019-11-04 23:49:26

标签: python python-3.x super method-resolution-order

我有使用超级继承和多重继承的代码。 该课程的结果是:

go A go!
go C go!
go B go!
go D go!

虽然我期望:

go A go!
go B go!
go D go!

从我的理解:D因为MRO调用了类B,因为go是在B中实现的。类B调用了其父类A的超级。A被执行了,那就可以了。然后我期望B继续执行,因此这意味着B被执行,最后D被执行。 但是,这当然是不正确的。为什么要输入C,因为在B中找到了go方法的定义,然后它不再在C中搜索。这就是MRO的工作方式。它创建于头等舱,现在不再搜索。完全困惑:(

 class A(object):
        def go(self):
            print("go A go!")

class B(A):
    def go(self):
        super(B, self).go()
        print("go B go!")

class C(A):
    def go(self):
        super(C, self).go()
        print("go C go!")

class D(B,C):
    def go(self):
        super(D, self).go()
        print("go D go!")

a = A()
b = B()
c = C()
d = D()

d.go()

3 个答案:

答案 0 :(得分:1)

Raymond Hettinger(Python核心开发人员)here展示了PyCon上的精彩视频。

主要收获是,超级不会叫你的父母,它叫你的后代的祖先。因此,每当您调用super时,它就会返回到您实际上在调用该方法的实例,并在MRO中查找下一个类型(即使具有多重继承,这也是保证的顺序,原因为何class D(B,C)class D(C, B)不同)。

在您的情况下,D的MRO为(D,B,C,A)。当D.go调用super时,它将调用B.go。当调用super时,它仍在使用D的MRO,因此它将移至下一个类型,在您的情况下为C,后者依次调用A。最终结果是您的打印语句在D的MRO中执行了相反的操作。

There is also a blog by RH here也涵盖了相同的主题,如果您无法观看youtube。

答案 1 :(得分:0)

调用d.go()时,每次对super的调用都使用调用实例的MRO(在这种情况下,d的MRO为D,B,C,A,对象。super不一定 必然指向其静态出现的类的父级。

这在B.go的定义中最明显。尽管B的定义对C一无所知,但其self参数是对类型为D的对象的引用,并且是D中的下一个类B之后的MRO是C,而不是A

答案 2 :(得分:0)

除极少数情况下,我个人不经常使用继承并避免多重继承。

这就像人们期望的那样棘手。

如果您通过先打印然后调用super稍微更改代码,我认为您会更好地理解事情的运作方式:

唯一真正的区别是,我先打印然后致电super。 除此之外,我仅使用lazier python3语法(无需从object显式继承,如果从方法中调用,则无需将参数传递给super(),它们将被自动正确地填充),但这不会改变任何东西。

class A:
        def go(self):
            print("go A go!")

class B(A):
    def go(self):
        print("go B go!")
        super().go()

class C(A):
    def go(self):
        print("go C go!")
        super().go()

class D(B,C):
    def go(self):
        print("go D go!")
        super().go()

b = B()
print(b.__class__)
print(b.__class__.__mro__)
d = D()
print(d.__class__)
print(d.__class__.__mro__)
d.go()

因此,您首先看到的是bd的类和MRO,这不足为奇。

如果D有一个方法,它将被调用;如果它不在B中寻找该方法,如果不在C中寻找该方法,如果不在A中寻找该方法

因此将首先调用D.go()

super(D, self).go()调用的

D.go()将在self.__class__.__mro__的MRO中查找下一个条目。记住我们在D,所以它看着B,在那里它将找到一个go方法并调用它。

现在,事情的表现与您期望的不同,go()中的B方法并没有像您期望的那样查看B的MRO,而是继续查看下一个self.__class__的MRO中的条目,它是C而不是您期望的A,因为self.__class__当然仍然是D

希望这有助于理解。

以上脚本的输出为:

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