Python 的 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"

答案

Guido 自己在他的博客文章Method Resolution Order (包括两次较早的尝试)中对此进行了合理的详细说明。

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

class Third(First, Second):
    ...

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

当继承开始跨越路径时,这种情况变得更加复杂(例如,如果FirstSecond继承)。阅读上面的链接以获取更多详细信息,但是简而言之,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 为[Fourth, Second, Third, First].

顺便说一句:如果 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'>)

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

super()调用在每个步骤中都会在 MRO 中找到下一个方法,这就是为什么 First 和 Second 也必须拥有它的原因,否则执行将在Second.__init__()的末尾停止。

这是我得到的:

>>> Third()
second
first
third

我想一点点地阐述一下答案,因为当我开始阅读如何在 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 撰写的有关方法解析顺序文章 ,在 python 2.3 之前,使用 “深度优先从左到右遍历” 来计算__init__解析顺序:

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

除去所有重复项(最后一个重复项除外)之后,我们得到:

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

因此,让我们关注当实例化Third类的实例时发生的情况,例如x = Third()

  1. 根据 MRO Third.__init__执行。
    • 打印Third(): entering
    • 然后执行super(Third, self).__init__()并 MRO 返回First.__init__ ,这将被调用。
  2. First.__init__执行。
    • 打印First(): entering
    • 然后执行super(First, self).__init__()并 MRO 返回Second.__init__ ,这将被调用。
  3. Second.__init__执行。
    • 打印Second(): entering
    • 然后super(Second, self).__init__()执行,MRO 返回被调用的object.__init__
  4. object.__init__执行(那里的代码中没有打印语句)
  5. 执行返回Second.__init__ ,然后显示Second(): exiting
  6. 执行返回到First.__init__ ,然后显示First(): exiting
  7. 执行返回到Third.__init__ ,然后显示Third(): exiting

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

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

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

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

这就是我解决以下问题的方法:具有用于初始化的不同变量的多重继承和具有相同函数调用的多个 MixIn。我必须显式地将变量添加到传递的 ** kwargs 中,并添加一个 MixIn 接口作为超级调用的端点。

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

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 Linerization。在此处阅读所有内容: https//www.python.org/download/releases/2.3/mro/

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

  1. 儿童
  2. 剩下
  3. 父母

调用方法时,该方法在 MRO 中的首次出现就是被调用的方法。任何不实现该方法的类都将被跳过。在该方法中对super任何调用都会在 MRO 中调用该方法的下一次出现。因此,在继承中放置类的顺序以及在方法中将对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

关于@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 ,而这只是对这些 “魔术变量” 真正有用且明智的使用之一。

另一个尚未涵盖的要点是传递用于初始化类的参数。由于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 可能被多次调用(取决于实现),则失败。

总而言之:合作继承以及用于初始化的超级参数和特定参数不能很好地协同工作。

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()可以找到 Third 中定义的init 。在该例程中调用 super 会调用 First 中定义的init 。 MRO = [第一,第二]。现在,对 First 中定义的init 的 super 的调用将继续搜索 MRO 并查找 Second 中定义的init ,任何对 super 的调用都会命中默认对象init 。我希望这个例子可以澄清这个概念。

如果您不从 First 呼叫 super。链停止,您将获得以下输出。

first 10
that's it