super()如何处理多重继承?例如,给定:

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()。__init__ refer to?我可以选择哪些运行吗?

我知道这与方法解析顺序(MRO)有关。


当前回答

你的代码和其他答案都是错误的。它们缺少前两个类中的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("third")

输出:

>>> Third()
second
first
third

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


如果在First和Second中没有super()调用,输出就会丢失Second:

>>> Third()
first
third

其他回答

整体

假设所有东西都来自object(如果不是你自己的),Python会根据你的类继承树计算一个方法解析顺序(MRO)。MRO满足3个性质:

一个阶层的孩子比他们的父母更重要 左父母先于右父母 一个类在MRO中只出现一次

如果不存在这样的顺序,Python将出错。它的内部工作原理是类祖先的C3线性化。点击这里阅读:https://www.python.org/download/releases/2.3/mro/

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

注意,你可以通过使用__mro__方法在python中看到MRO。

例子

下面所有的例子都有菱形类继承,如下所示:

    Parent
    /   \
   /     \
Left    Right
   \     /
    \   /
    Child

MRO是:

孩子 左 正确的 父

您可以通过调用Child来测试这一点。__mro__,返回:

(__main__.Child, __main__.Left, __main__.Right, __main__.Parent, object)

每一种方法都以超第一

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

孩子()输出:

parent
right
left
child
    

在每个方法中都有超级最后

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
left
right
parent

当不是所有类都调用super时

如果不是继承链中的所有类都调用super,那么继承顺序最重要。例如,如果Left不调用super,那么Right和Parent上的方法永远不会被调用:

class Parent(object):
    def __init__(self):
        print("parent")
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print("left")

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
left

或者,如果Right没有调用super, Parent仍然被跳过:

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

class Child(Left, Right):
    def __init__(self):
        print("child")
        super(Child, self).__init__()

在这里,Child()输出:

child
left
right

调用特定父节点上的方法

如果你想访问一个特定父类的方法,你应该直接引用这个类,而不是使用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):
        Parent.__init__(self)
        print("child")

在这种情况下,Child()输出:

parent
child

我知道这并没有直接回答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__()不会被调用。

关于@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,它真的很混乱。你可以通过使用关键字参数来避免这种情况:

class A(object):
  def __init__(self, *args, a, **kwargs):
    print("A", a)

等等。

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

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

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

理解这最后一部分至关重要。让我们再考虑一下这个例子:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

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

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

删除所有重复项后,除了最后一个,我们得到:

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

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

According to MRO Third.__init__ executes. prints Third(): entering then super(Third, self).__init__() executes and MRO returns First.__init__ which is called. First.__init__ executes. prints First(): entering then super(First, self).__init__() executes and MRO returns Second.__init__ which is called. Second.__init__ executes. prints Second(): entering then super(Second, self).__init__() executes and MRO returns object.__init__ which is called. object.__init__ executes (no print statements in the code there) execution goes back to Second.__init__ which then prints Second(): exiting execution goes back to First.__init__ which then prints First(): exiting execution goes back to Third.__init__ which then prints Third(): exiting

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

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

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

Guido在他的博客文章Method Resolution Order(包括两个早期的尝试)中对此进行了详细的描述。

在你的例子中,Third()将调用First.__init__。Python按照从左到右列出的顺序在类的父类中查找每个属性。在本例中,我们正在寻找__init__。如果你定义

class Third(First, Second):
    ...

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

当继承开始交叉路径时(例如First从Second继承),这种情况会变得更加复杂。阅读上面的链接了解更多细节,但是,简而言之,Python将试图保持每个类在继承列表中出现的顺序,从子类本身开始。

例如,如果你有:

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为[第四、第二、第三、第一]。

顺便说一下:如果Python无法找到一致的方法解析顺序,它将引发异常,而不是退回到可能使用户感到惊讶的行为。

模棱两可的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是如何构造的。他们不打算打印“第一\第二\第三”或其他什么。当然,您可以(也应该)尝试一下这个示例,添加super()调用,看看会发生什么,并更深入地理解Python的继承模型。但我的目标是保持简单,并展示MRO是如何构建的。正如我解释的那样:

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