python的super()是如何处理多重继承的?

我是Python面向对象编程的新手,遇到了一些麻烦理解super()函数(新样式类),特别是涉及到多重继承时。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
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()类会继承这两个构造函数方法吗?如果是,那么哪个将使用super()运行,为什么?

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


Guido在他的博客文章方法解析顺序(包括之前的两次尝试)中给出了相当多的细节。

在您的示例中,Third()将调用First.__init__。Python查找类的父类中的每个属性,因为它们是从左到右列出的。在本例中,我们正在寻找__init__。如果你定义

1
2
class Third(First, Second):
    ...

Python将从查看First开始,如果First没有属性,那么它将查看Second

当继承开始交叉路径时,这种情况变得更加复杂(例如,如果First继承自Second)。阅读上面的链接了解更多细节,但是,简单地说,Python将尝试维护继承列表中每个类出现的顺序,从子类本身开始。

例如,如果你有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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不能找到一致的方法解析顺序,它将引发异常,而不是退回到可能会让用户感到惊讶的行为。

编辑添加一个模糊的MRO的例子:

1
2
3
4
5
6
7
8
9
10
11
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会提出一个错误:

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

编辑:我看到一些人认为上面的例子缺少super()调用,所以让我解释一下:这些例子的重点是展示如何构造MRO。它们不打算打印"firstsecond hird"或其他内容。当然,您可以也应该尝试一下这个示例,添加super()调用,看看会发生什么,并对Python的继承模型有更深入的理解。但我在这里的目标是保持简单,并展示如何构建MRO。就像我解释的那样:

1
2
3
4
5
>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)


您的代码和其他答案都有bug。它们缺少前两个类中的super()调用,而这两个类是协作子类化工作所必需的。

以下是固定版本的守则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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("third")

super()调用在每一步都在MRO中找到下一个方法,这就是为什么第一和第二步也必须有它,否则执行将在Second.__init__()结束时停止。

我得到的是:

1
2
3
4
>>> Third()
second
first
third


我想用无生气的方式来详细说明这个答案,因为当我开始阅读如何在Python的多重继承层次结构中使用super()时,我并没有立即得到它。

您需要了解的是,super(MyClass, self).__init__()根据完整继承层次结构上下文中使用的方法解析排序(MRO)算法提供了下一个__init__方法。

最后一部分是理解的关键。让我们再考虑一下这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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关于方法解析顺序的这篇文章,解析__init__的顺序是使用"深度优先从左到右遍历"来计算的(在Python 2.3之前):

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

除最后一个副本外,删除所有副本后,得到:

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

因此,让我们看看在实例化Third类的实例时发生了什么,例如x = Third()

根据亚南先生的说法,第三个叫做第一。

接下来,根据MRO,在__init__方法内部super(Third,
self).__init__()
解析为__init__ First方法,其中被调用。

第一个的__init__内部调用第二个的__init__,因为这是MRO的命令!

第二次super(Second, self).__init__()调用的__init__内部对象的__init__,它等于零。在那之后"第二"是印刷。

super(First, self).__init__()完成后,"第一"是印刷。

super(Third, self).__init__()完成后,打印"that's it"。

这就详细说明了为什么实例化Third()会导致:

1
2
3
4
>>> x = Third()
second
first
that's it

MRO算法已经从Python 2.3开始进行了改进,可以在复杂的情况下很好地工作,但是我认为在大多数情况下,使用"深度优先的从左到右遍历"+"除最后一个外删除重复项"仍然可以工作(如果不是这样,请评论)。请务必阅读Guido的博客文章!


这就是众所周知的Diamond问题,页面上有一个关于Python的条目,但是简而言之,Python将从左到右调用超类的方法。


这是我如何解决的问题,有多个继承与不同的变量初始化和有多个混合与相同的函数调用。我必须显式地向传递的**kwargs添加变量,并添加MixIn接口作为超级调用的端点。

这里A是一个可扩展的基类,BC是MixIn类,它们都提供函数fAB都在它们的__init__中期望参数v,而C期望参数w。函数f接受一个参数yQ继承自所有三个类。MixInFBC的mixin接口。

IPython笔记本的这段代码Github回购与代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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)


我知道这并不能直接回答super()问题,但我觉得它足够相关,可以分享。

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


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__()将不会被调用。


整体

假设所有内容都来自object(如果不是,则由您自己决定),Python根据类继承树计算方法解析顺序(MRO)。MRO满足3个性质:

一个班的孩子比他们的父母先到左父母先于右父母类只在MRO中出现一次

如果不存在这样的排序,则Python错误。其内部工作原理是类祖先的C3线性化。请在这里阅读:https://www.python.org/download/releases/2.3/mro/

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

孩子左正确的父

当一个方法被调用时,该方法在MRO中的第一个出现就是被调用的那个。任何没有实现该方法的类都将被跳过。该方法中对super的任何调用都将调用该方法在MRO中的下一次出现。因此,在继承中放置类的顺序很重要,在方法中调用super的位置也很重要。

super首先在每个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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()输出:

1
2
3
4
parent
right
left
child

super最后在每个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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()输出:

1
2
3
4
child
left
right
parent


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

在线运行的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)

结果:

1
2
3
4
A None
B hello
A1 6
B1 5

你也可以用它们来表示位置:

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

但是你必须记住MRO,它真的很让人困惑。

我可能有点烦人,但是我注意到人们每次重写一个方法时都忘记使用*args**kwargs,而这是这些"魔法变量"中为数不多的真正有用和理智的用法之一。


另一个尚未涉及的点是传递初始化类的参数。由于super的目标依赖于子类,传递参数的唯一好方法就是将它们打包在一起。然后注意不要使用具有不同含义的相同参数名称。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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()

给:

1
2
3
4
5
[<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可能被多次调用,这取决于实现。

总结:协作继承和用于初始化的超特定参数并不能很好地协同工作。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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()

输出是

1
2
3
first 10
second 20
that's it

调用Third()查找在Third中定义的init。在那个例程中调用super调用First中定义的init。MRO =(一、二)。现在,调用第一个定义的init中的super将继续搜索MRO,并找到第二个定义的init,对super的任何调用都将命中缺省对象init。我希望这个例子能阐明这个概念。

如果你不先打电话给super。链停止,您将得到以下输出。

1
2
first 10
that's it


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

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

在这种情况下,解释器不会过滤掉对象类,因为它是重复的,而是因为Second出现在头部位置,而不是出现在层次结构子集的尾部位置。而对象只出现在尾部位置,在C3算法中不被认为是确定优先级的强位置。

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

类C加上它的双亲P1 P2。= L(P1, P2,…它的父母的列表P1 P2 ..

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

第三个的线性化可以计算如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    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():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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"

很明显,将如何解决这个方法

1
2
3
4
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"


在学习pythonthehardway的过程中,我学到了一个叫做super()的东西,如果没有弄错的话,它是一个内置函数。调用super()函数可以帮助继承通过父类和"兄弟姐妹",并帮助您看得更清楚。我仍然是一个初学者,但是我喜欢分享我在python2.7中使用super()的经验。

如果您阅读了此页面的注释,您将听说方法解析顺序(Method Resolution Order, MRO),方法是您编写的函数,MRO将使用深度从左到右的模式进行搜索和运行。你可以做更多的研究。

通过添加super()函数

1
super(First, self).__init__() #example for class First.

通过在super()中添加每个实例和每一个实例,可以将多个实例和"家庭"与super()连接起来。它会执行这些方法,遍历它们确保你没有遗漏!然而,把它们加在前面或后面确实会有所不同,你会知道你是否做过学python的hardway练习44。让乐趣开始吧!

举个例子,你可以复制;粘贴并尝试运行它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

它是如何运行的?fifth()的实例将是这样的。每个步骤从一个类到添加超函数的类。

1
2
3
4
1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

家长找到了,还会继续往第三和第四!!

1
2
3
4
5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

现在已经访问了所有带有super()的类!父类已经找到并执行,现在它将继续在继承中解压函数以完成代码。

1
2
3
4
5
6
9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

上述计划的结果:

1
2
3
4
5
6
7
8
9
fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

对于我来说,通过添加super(),我可以更清楚地看到python将如何执行我的代码,并确保继承可以访问我想要的方法。