Python的super()如何与多重继承一起工作?

时间:2010-07-18 21:40:26

标签: python multiple-inheritance

我是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"

我没有得到的是:Third()类是否会继承构造函数方法?如果是,那么将使用super()运行哪一个?为什么?

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

18 个答案:

答案 0 :(得分:607)

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将是[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。他们打算打印“first \ nsecond \ third”或其他什么。当然,您可以 - 并且应该使用示例,添加super()调用,查看会发生什么,并深入了解Python的继承模型。但我的目标是保持简单并展示MRO是如何构建的。它按照我解释的那样构建:

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

答案 1 :(得分:217)

您的代码和其他答案都是错误的。他们缺少合作子类化工作所需的前两个类中的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

答案 2 :(得分:153)

我想详细说明the answer by lifeless因为当我开始阅读Python中多重继承层次结构中如何使用super()时,我确实没有立即得到它。

您需要了解的是super(MyClass, self).__init__()根据在上下文中使用的方法解析排序(MRO)算法提供下一个 __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的

According to this article about Method Resolution Order,使用&#34;深度优先从左到右的遍历计算解决__init__的顺序(在Python 2.3之前)。 :

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

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

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

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

  1. 根据MRO __init__,第三名被称为第一名。

  2. 接下来,根据MRO,__init__方法super(Third, self).__init__()内部解析为First的__init__方法, 被叫。

  3. 首先__init__的{​​{1}}内部会调用super(First, self).__init__()秒,因为这是MRO的指示!

  4. 第二次__init__次来电__init__次来电 对象的super(Second, self).__init__(),无关紧要。之后 的&#34;第二&#34;打印

  5. __init__完成后, 的&#34;第一&#34;打印

  6. super(First, self).__init__()完成后,&#34;它是&#34;打印

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

    super(Third, self).__init__()

    MRO算法已从Python 2.3开始改进,在复杂情况下运行良好,但我想使用&#34;深度优先从左到右的遍历&#34; +&#34;删除最后一个&#34;在大多数情况下仍然有效(如果不是这样,请发表评论)。请务必阅读Guido的博客文章!

答案 3 :(得分:49)

这被称为Diamond Problem,该页面在Python上有一个条目,但简而言之,Python将从左到右调用超类的方法。

答案 4 :(得分:25)

这是我如何解决使用不同变量进行多重继承以进行初始化以及具有多个具有相同函数调用的MixIns的问题。我必须明确地添加变量来传递** kwargs并添加一个MixIn接口作为超级调用的端点。

此处A是一个可扩展的基类,BC是提供函数f的MixIn类。 AB都期望v中的参数__init__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)

答案 5 :(得分:19)

我理解这不会直接回答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的{​​{1}}将不会被调用。

答案 6 :(得分:18)

总体

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

  • 班级中的孩子来到他们的父母面前
  • 左父母来到右父母之前
  • 课程仅在MRO中出现一次

如果不存在此类排序,则会出现Python错误。这个的内部工作原理是类祖先的C3 Linerization。在此处阅读所有相关内容:https://www.python.org/download/releases/2.3/mro/

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

  1. 右键
  2. 调用方法时,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
    

答案 7 :(得分:15)

关于@calfzhou's comment,您通常可以使用**kwargs

Online running example

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时他们覆盖了一种方法,而它是少数几个真正有用且理智的人之一使用这些神奇的变量&#39;。

答案 8 :(得分:11)

另一个尚未涉及的问题是传递用于初始化类的参数。由于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可能被调用则会失败多次,取决于实施。

总结:合作继承以及初始化的超级和特定参数并没有很好地协同工作。

答案 9 :(得分:4)

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

Call to Third()找到Third中定义的 init 。并且在该例程中调用super来调用First中定义的 init 。 MRO = [第一,第二]。 现在在First中定义的 init 中调用super将继续搜索MRO并找到在Second中定义的 init ,并且对super的任何调用都将命中默认对象 init 。我希望这个例子澄清了这个概念。

如果你不从First打电话给super。链条停止,您将获得以下输出。

first 10
that's it

答案 10 :(得分:3)

在学习python的方式中,如果没有记错的话,我会学习称为super()的内置函数。调用super()函数可以帮助继承通过父级和“同级”,并帮助您更清晰地了解。我仍然是一个初学者,但我喜欢分享我在python2.7中使用此super()的经验。

如果您已通读此页中的注释,则将听到方法解析顺序(MRO),该方法是您编写的函数,MRO将使用深度从左到右的方案进行搜索和跑。您可以对此进行更多研究。

通过添加super()函数

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

您可以通过添加每个实例和每个实例中的每个实例,将多个实例和“家族”与super()连接。然后它将执行这些方法,仔细检查它们,并确保您没有错过!但是,在之前或之后添加它们确实会有所不同,您将知道您是否已经完成了学习Python的难度练习44。让乐趣开始吧!

以下面的示例为例,您可以复制并粘贴并尝试运行它:

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

它如何运行? third()的实例将像这样。每个步骤都在添加超级功能的类之间进行。

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)

找到了父母,它将继续到第三和第四!!

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

现在所有具有super()的类都已被访问!已经找到并执行了父类,现在它继续对继承中的函数进行拆箱以完成代码。

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

以上程序的结果:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

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

答案 11 :(得分:2)

我想在顶部添加what @Visionscaper says

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

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

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

  • C类
  • 加上合并
    • 其父母的线性化P1,P2,.. = L(P1,P2,...)和
    • 其父母名单P1,P2,..

Linearised Merge是通过选择显示为列表头部而不是尾部的公共类来完成的,因为订单很重要(将在下面说明)

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"

答案 12 :(得分:1)

也许仍然可以添加一些东西,一个带有Django rest_framework和装饰器的小例子。这提供了对隐式问题的答案:“我为什么仍要这样做?”

如前所述:我们使用的是Django rest_framework,并且使用的是通用视图,对于数据库中每种类型的对象,我们发现自己都有一个视图类,该类为对象列表提供GET和POST,而另一个视图为单个对象提供GET,PUT和DELETE的类。

现在,我们要用Django的login_required装饰POST,PUT和DELETE。请注意,这是如何触及两个类的,但并非触及两个类中的所有方法。

解决方案可能会经历多个继承。

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

与其他方法类似。

在具体类的继承列表中,我将在LoginToPost之前添加ListCreateAPIView,在LoginToPutOrDelete之前添加RetrieveUpdateDestroyAPIView。我的具体班级的get将保持不变。

答案 13 :(得分:1)

在python 3.5+中,继承看起来是可预测的,对我来说非常好。 请查看以下代码:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


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


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

输出:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

如您所见,它为每个继承的链以与继承相同的顺序恰好一次调用foo。您可以通过致电.mro来获得该订单:

第四->第三->第一->第二->基础->对象

答案 14 :(得分:1)

发布此答案以供将来参考。

Python多重继承应使用菱形模型,并且函数签名在模型中不应更改。

    A
   / \
  B   C
   \ /
    D

示例代码段应为;-

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

A类为object

答案 15 :(得分:1)

考虑从子类中调用super().Foo()方法解析顺序(MRO)方法是解决方法调用的顺序。

情况1:单一继承

在这种情况下,将在层次结构中搜索super()。Foo(),并将考虑最接近的实现(如果找到),否则引发Exception。在层次结构中任何访问的子类及其父类之间,“ ”关系始终为True。但是这个故事在多重继承中并不总是一样。

案例2:多重继承

在这里,当搜索super()。Foo()实现时,层次结构中每个被访问的类可能具有关系。考虑以下示例:

class A(object): pass
class B(object): pass
class C(A): pass
class D(A): pass
class E(C, D): pass
class F(B): pass
class G(B): pass
class H(F, G): pass
class I(E, H): pass

在这里,I是层次结构中的最低类。 I的层次结构图和MRO将为

enter image description here

(红色数字显示MRO)

MRO为I E C D A H F G B object

请注意,只有访问了X类的所有子类(继承自该类的所有子类)(即,您永远不要访问带有箭头的类)低于您尚未访问的页面。

在这里,请注意,在访问C类之后,DC之间虽然没有{strong>是关系,但仍被访问了D (但两者都带有A)。这是super()与单一继承不同的地方。

考虑一个稍微复杂的示例:

enter image description here

(红色数字显示MRO)

MRO为I E C H D A F G B object

在这种情况下,我们从IE再到C。下一步将是A,但是我们还没有访问D的子类A。但是,我们无法访问D,因为我们尚未访问H的子类D。叶子H是下一个要访问的课程。请记住,如果可能的话,我们尝试在层次结构中向上移动,因此我们访问其最左侧的超类D。在D之后,我们访问了A,但是由于我们还没有访问FGB,所以我们无法拒绝。这些类按顺序对I的MRO进行四舍五入。

请注意,任何类在MRO中都不能出现多次。

这是super()在继承层次结构中查找的方式。

资源来源:Python编程的Richard L Halterman基础知识

答案 16 :(得分:1)

考虑子 AB,其中父 AB 在其构造函数中具有关键字参数。

  A    B
   \  /
    AB

要初始化 AB,您需要显式调用父类构造函数,而不是使用 super()

示例:

class A():
    def __init__(self, a="a"):
        self.a = a
        print(f"a={a}")
    
    def A_method(self):
        print(f"A_method: {self.a}")

class B():
    def __init__(self, b="b"):
        self.b = b
        print(f"b={b}")
    
    def B_method(self):
        print(f"B_method: {self.b}")
    
    def magical_AB_method(self):
        print(f"magical_AB_method: {self.a}, {self.b}")

class AB(A,B):
    def __init__(self, a="A", b="B"):
        # super().__init__(a=a, b=b) # fails!
        A.__init__(self,a=a)
        B.__init__(self,b=b)
        self.A_method()
        self.B_method()
        self.magical_AB_method()


A()
>>> a=a

B()
>>> b=b

AB()
>>> a=A
>>> b=B
>>> A_method: A
>>> B_method: B

为了证明两个父级组合成子级,请考虑在类 magical_AB_method 中定义的 B。当从 B 的实例调用时,该方法失败,因为它无法访问 A 内的成员变量。但是,当从子 AB 的实例调用时,此方法有效,因为它从 A 继承了所需的成员变量。

B().magical_AB_method()
>>> AttributeError: 'B' object has no attribute 'a'

AB().magical_AB_method()
>>> magical_AB_method: A, B

答案 17 :(得分:0)

如果您尝试继承的每个类都有自己的 init 位置参数,只需调用每个类自己的 init 方法,如果尝试从多个对象继承,则不要使用 super。

class A():
    def __init__(self, x):
        self.x = x

class B():
    def __init__(self, y, z):
        self.y = y
        self.z = z

class C(A, B):
    def __init__(self, x, y, z):
        A.__init__(self, x)
        B.__init__(self, y, z)

>>> c = C(1,2,3)
>>>c.x, c.y, c.z 
(1,2,3)