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

问题描述 投票:733回答:13

我是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)有关。

python multiple-inheritance
13个回答
617
投票

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

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

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

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

first 10
that's it

3
投票

在learnpythonthehardway,我学习了一些名为super()的内置函数,如果没有弄错的话。调用super()函数可以帮助继承传递父和兄弟姐妹,并帮助您看清楚。我仍然是初学者,但我喜欢分享我在python2.7中使用这个super()的经验。

如果您已阅读本页中的注释,您将听到方法解决顺序(MRO),该方法是您编写的功能,MRO将使用深度优先从左到右的方案进行搜索和运行。你可以做更多的研究。

通过添加super()函数

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

您可以使用super()连接多个实例和“系列”,方法是添加每个实例和每个人。它将执行方法,通过它们,并确保你没有错过!然而,在你之前或之后添加它们确实会产生影响,你会知道你是否已经完成了这次学习的艰苦训练。让乐趣开始!

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

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

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

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如何执行我的编码并确保继承可以访问我想要的方法。


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"

1
投票

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

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

现在POST,PUT和DELETE我们想用Django的login_required装饰。注意这会触及两个类,但不会触及任何一个类中的所有方法。

解决方案可以通过多重继承。

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)

同样适用于其他方法。

在我的具体类的继承列表中,我会在LoginToPostListCreateAPIView之前添加我的LoginToPutOrDelete。我的具体课程'RetrieveUpdateDestroyAPIView将保持未修饰。


219
投票

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

156
投票

我想详细说明一下the answer by lifeless,因为当我开始阅读如何在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撰写的According to this article about Method Resolution Order,使用“深度优先从左到右遍历”计算(在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的博客文章!


50
投票

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


25
投票

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

这里A是一个可扩展的基类,BC是MixIn类,它们都提供f功能。 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)

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


19
投票

总体

假设所有内容都来自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

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时,他们覆盖了一种方法,而这是少数真正有用和理智的使用这些“魔术变量”之一。


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可能被多次调用,则取决于实现,将失败。

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

© www.soinside.com 2019 - 2024. All rights reserved.