Python的super()如何处理多重inheritance?

在Python面向对象的编程方面我非常新,我很难理解super()函数(新样式类),特别是在涉及到多重inheritance的时候。

例如,如果你有像这样的东西:

 class First(object): def __init__(self): print "first" class Second(object): def __init__(self): print "second" class Third(First, Second): def __init__(self): super(Third, self).__init__() print "that's it" 

我没有得到的是: Third()类inheritance两个构造方法吗? 如果是的话,那么哪一个会用super()运行,为什么呢?

而如果你想运行另一个呢? 我知道这与Python方法parsing顺序( MRO )有关。

Guido自己在他的博客文章Method Resolution Order (包括两次较早的尝试)中详细描述了这个细节。

在你的例子中, Third()将调用First.__init__ 。 Python会在类的父项中查找从左到右列出的每个属性。 在这种情况下,我们正在寻找__init__ 。 所以,如果你定义

 class Third(First, Second): ... 

Python将首先查看First ,如果First没有该属性,那么它将查看Second

当inheritance开始跨越path时,这种情况变得更加复杂(例如,如果First Secondinheritance)。 阅读上面的链接了解更多细节,但是,简而言之,Python将尝试维护每个类在inheritance列表中出现的顺序,从子类本身开始。

所以,举个例子,如果你有:

 class First(object): def __init__(self): print "first" class Second(First): def __init__(self): print "second" class Third(First): def __init__(self): print "third" class Fourth(Second, Third): def __init__(self): super(Fourth, self).__init__() print "that's it" 

MRO将是[Fourth, Second, Third, First].

顺便说一句:如果Python找不到一个连贯的方法parsing顺序,它会引发一个exception,而不是回到可能让用户感到惊讶的行为。

编辑添加一个模棱两可的MRO的例子:

 class First(object): def __init__(self): print "first" class Second(First): def __init__(self): print "second" class Third(First, Second): def __init__(self): print "third" 

Third MRO应该是[First, Second]还是[Second, First] ? 没有明显的期望,Python会引发一个错误:

 TypeError: Error when calling the metaclass bases Cannot create a consistent method resolution order (MRO) for bases Second, First 

编辑:我看到有几个人认为上面的例子没有super()调用,所以让我解释一下:例子的要点是展示MRO是如何构build的。 他们打算打印“第一\ nsecond \第三”或其他。 当然,你可以 – 也应该玩这个例子,添加super()调用,看看会发生什么,并且更深入地理解Python的inheritance模型。 但是我的目标是保持简单,展示MRO是如何构build的。 正如我所解释的那样:

 >>> Fourth.__mro__ (<class '__main__.Fourth'>, <class '__main__.Second'>, <class '__main__.Third'>, <class '__main__.First'>, <type 'object'>) 

你的代码和其他答案都是错误的。 他们错过了合作子类工作所需要的前两个类中的super()调用。

这是一个固定版本的代码:

 class First(object): def __init__(self): super(First, self).__init__() print("first") class Second(object): def __init__(self): super(Second, self).__init__() print("second") class Third(First, Second): def __init__(self): super(Third, self).__init__() print("that's it") 

super()调用在每个步骤都会在MRO中find/ next方法/,这就是为什么First和Second也必须有它,否则在Second.__init__结束时停止执行。

这是我得到的:

 >>> Third() second first that's it 

我想简单地说一些无生命的答案,因为当我开始阅读关于如何在Python的多重inheritance层次结构中使用super()的时候,我没有马上得到它。

你需要理解的是, super(MyClass, self).__init__() 在完整的inheritance层次结构的上下文中根据使用的方法parsing顺序(MRO)algorithm提供下一个 __init__方法。

这最后一部分是至关重要的理解。 再来看一下这个例子:

 class First(object): def __init__(self): super(First, self).__init__() print "first" class Second(object): def __init__(self): super(Second, self).__init__() print "second" class Third(First, Second): def __init__(self): super(Third, self).__init__() print "that's it" 

根据这篇关于 Guido van Rossum的方法parsing顺序的文章 ,使用“深度优先从左到右遍历”来计算(在Python 2.3之前)parsing__init__的顺序:

 Third --> First --> object --> Second --> object 

除去最后一个重复项之后,我们得到:

 Third --> First --> Second --> object 

所以,让我们来看看当我们实例化Third类的一个实例时会发生什么,例如x = Third()

  1. 根据MRO的__init__第一个被称为第一个。

  2. 接下来,根据MRO,在__init__方法中super(Third, self).__init__()parsing为First的__init__方法,被调用。

  3. 第一个super(First, self).__init__()__init__调用了第二个__init__ ,因为这是MRO指定的!

  4. 第二个super(Second, self).__init__()__init__调用了对象的__init__ ,这个__init__什么也没有。 之后, 打印“秒”

  5. super(First, self).__init__()完成后, 打印“first”

  6. super(Third, self).__init__()完成后, “就是这样”被打印

这就详细说明了为什么将Third()结果实例化为:

 >>> x = Third() second first that's it 

从Python 2.3开始,MROalgorithm已经得到了改进,在复杂的情况下能够很好地工作,但是我想大多数情况下,使用“深度优先从左到右的遍历”+“去除重复期望的最后一个”如果不是这样的话,请发表评论)。 一定要阅读圭多的博客文章!

这就是所谓的钻石问题 ,页面上有一个关于Python的入口,但是总之,Python会从左到右调用超类的方法。

这是我如何解决问题的多个inheritance与不同的variables初始化和具有相同的函数调用多个MixIns。 我不得不显式添加variables来传递** kwargs,并添加一个MixIn接口作为超级调用的端点。

这里A是可扩展的基类, BC是提供函数f MixIn类。 AB都希望参数v__init__C期望w 。 函数f取一个参数yQ从三个类inheritance。 MixInFBC的混合接口。

  • 这个代码的IPython笔记本
  • Github回购代码示例
 class A(object): def __init__(self, v, *args, **kwargs): print "A:init:v[{0}]".format(v) kwargs['v']=v super(A, self).__init__(*args, **kwargs) self.v = v class MixInF(object): def __init__(self, *args, **kwargs): print "IObject:init" def f(self, y): print "IObject:y[{0}]".format(y) class B(MixInF): def __init__(self, v, *args, **kwargs): print "B:init:v[{0}]".format(v) kwargs['v']=v super(B, self).__init__(*args, **kwargs) self.v = v def f(self, y): print "B:f:v[{0}]:y[{1}]".format(self.v, y) super(B, self).f(y) class C(MixInF): def __init__(self, w, *args, **kwargs): print "C:init:w[{0}]".format(w) kwargs['w']=w super(C, self).__init__(*args, **kwargs) self.w = w def f(self, y): print "C:f:w[{0}]:y[{1}]".format(self.w, y) super(C, self).f(y) class Q(C,B,A): def __init__(self, v, w): super(Q, self).__init__(v=v, w=w) def f(self, y): print "Q:f:y[{0}]".format(y) super(Q, self).f(y) 

关于@ calfzhou的评论 ,你可以像往常一样使用**kwargs

在线运行的例子

 class A(object): def __init__(self, a, *args, **kwargs): print("A", a) class B(A): def __init__(self, b, *args, **kwargs): super(B, self).__init__(*args, **kwargs) print("B", b) class A1(A): def __init__(self, a1, *args, **kwargs): super(A1, self).__init__(*args, **kwargs) print("A1", a1) class B1(A1, B): def __init__(self, b1, *args, **kwargs): super(B1, self).__init__(*args, **kwargs) print("B1", b1) B1(a1=6, b1=5, b="hello", a=None) 

结果:

 A None B hello A1 6 B1 5 

你也可以在位置上使用它们:

 B1(5, 6, b="hello", a=None) 

但是你必须记住MRO,这真是令人困惑。

我可能有点讨厌,但是我注意到,当他们重写一个方法的时候,人们忘记了每次使用*args**kwargs ,而这是这些“魔术variables”的less数真正有用和理智的用法之一。

我明白这不是直接回答super()问题,但是我觉得它足够分享。

还有一种方法可以直接调用每个inheritance的类:

class First(object): def __init__(self): print '1' class Second(object): def __init__(self): print '2' class Third(First, Second): def __init__(self): Second.__init__(self)
class First(object): def __init__(self): print '1' class Second(object): def __init__(self): print '2' class Third(First, Second): def __init__(self): Second.__init__(self) 

只要注意,如果你这样做,你将不得不手动调用,因为我很确定First __init__()将不会被调用。

另一个尚未涉及的问题是传递参数来初始化类。 由于super的目的地依赖于子类,所以传递参数的唯一好方法就是将它们打包在一起。 然后小心不要有相同的参数名称和不同的含义。

例:

 class A(object): def __init__(self, **kwargs): print('A.__init__') super().__init__() class B(A): def __init__(self, **kwargs): print('B.__init__ {}'.format(kwargs['x'])) super().__init__(**kwargs) class C(A): def __init__(self, **kwargs): print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b'])) super().__init__(**kwargs) class D(B, C): # MRO=D, B, C, A def __init__(self): print('D.__init__') super().__init__(a=1, b=2, x=3) print(D.mro()) D() 

得到:

 [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] D.__init__ B.__init__ 3 C.__init__ with 1, 2 A.__init__ 

直接调用超类__init__以更直接地分配参数是诱人的,但是如果在超类中存在super调用并且/或者MRO被改变并且类A可能被多次调用,则取决于实现。

总而言之:协同inheritance和超级特定参数的初始化不能很好地协同工作。

总体

假设所有东西都是从objectinheritance下来的(如果不是,那么你自己就是自己的),Python会根据你的类inheritance树来计算方法parsing顺序(MRO)。 MRO满足3个属性:

  • 一个class级的孩子来到他们的父母面前
  • 左派父母来到右派父母面前
  • 一个课只在MRO中出现一次

如果不存在这样的sorting,Python错误。 这个内部工作是类祖先的C3线化。 在这里阅读所有内容: https : //www.python.org/download/releases/2.3/mro/

因此,在以下两个例子中,它是:

  1. 儿童
  2. 剩下

当一个方法被调用时,MRO中第一次出现该方法就是被调用的方法。 任何不执行该方法的类都会被跳过。 在该方法中super调用将在MRO中调用该方法的下一次出现。 因此,在inheritance中放置类的顺序以及在方法中将调用放到super位置都很重要。

每种方法都super优先

 class Parent (object): def __init__(self): super(Parent, self).__init__() print "parent" class Left(Parent): def __init__(self): super(Left, self).__init__() print "left" class Right(Parent): def __init__(self): super(Right, self).__init__() print "right" class Child(Left, Right): def __init__(self): super(Child, self).__init__() print "child" 

Child()输出:

 parent right left child 

在每个方法super最后

 class Parent (object): def __init__(self): print "parent" super(Parent, self).__init__() class Left(Parent): def __init__(self): print "left" super(Left, self).__init__() class Right(Parent): def __init__(self): print "right" super(Right, self).__init__() class Child(Left, Right): def __init__(self): print "child" super(Child, self).__init__() 

Child()输出:

 child left right parent 
 class First(object): def __init__(self, a): print "first", a super(First, self).__init__(20) class Second(object): def __init__(self, a): print "second", a super(Second, self).__init__() class Third(First, Second): def __init__(self): super(Third, self).__init__(10) print "that's it" t = Third() 

输出是

 first 10 second 20 that's it 

调用Third()定位在第三个定义的init 。 在那个例程中调用super来调用在First中定义的init 。 MRO = [第一,第二]。 现在在First中定义的init中调用super将继续searchMRO,并find第二个定义的init ,任何对super的调用都将触发缺省对象init 。 我希望这个例子澄清这个概念。

如果你没有从First调用超级。 链停止,你会得到以下输出。

 first 10 that's it 

我想补充一下@Visionscaper在顶部所说的话:

 Third --> First --> object --> Second --> object 

在这种情况下,解释器不会过滤掉对象类,因为它是重复的,而不是它的原因,因为第二次出现在头部位置并且不出现在层次结构子集中的尾部位置。 虽然对象只出现在尾部位置,并不被认为是C3algorithm中确定优先级的强有力的位置。

C类的线性化(mro)L(C)是

  • C类
  • 加上合并
    • 对其父母P1,P2,… = L(P1,P2,…)进行线性化处理
    • 其父母P1,P2的名单

线性化合并是通过select显示为列表头部而不是尾部的公共类来完成的,因为顺序很重要(将在下面变得清楚)

Third的线性化可以如下计算:

  L(O) := [O] // the linearization(mro) of O(object), because O has no parents L(First) := [First] + merge(L(O), [O]) = [First] + merge([O], [O]) = [First, O] // Similarly, L(Second) := [Second, O] L(Third) := [Third] + merge(L(First), L(Second), [First, Second]) = [Third] + merge([First, O], [Second, O], [First, Second]) // class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists // class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, = [Third, First] + merge([O], [Second, O], [Second]) // class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3 = [Third, First, Second] + merge([O], [O]) = [Third, First, Second, O] 

因此,对于以下代码中的super()实现:

 class First(object): def __init__(self): super(First, self).__init__() print "first" class Second(object): def __init__(self): super(Second, self).__init__() print "second" class Third(First, Second): def __init__(self): super(Third, self).__init__() print "that's it" 

这个方法如何解决就变得很明显了

 Third.__init__() ---> First.__init__() ---> Second.__init__() ---> Object.__init__() ---> returns ---> Second.__init__() - prints "second" - returns ---> First.__init__() - prints "first" - returns ---> Third.__init__() - prints "that's it"