首页 文章

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

提问于
浏览
672

我是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) .

12 回答

  • 136

    我想详细说明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,使用"depth-first left-to-right traversal"计算解决 __init__ 的顺序(在Python 2.3之前):

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

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

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

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

    • 根据MRO __init__ 的第三名被称为第一名 .

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

    • 第一 super(First, self).__init__()__init__ 调用 __init__ 秒,因为那是MRO的指示!

    • 第二 super(Second, self).__init__()__init__ 调用对象的 __init__ ,这无效 . 之后 "second" is printed .

    • 完成 super(First, self).__init__() 后, "first" is printed .

    • super(Third, self).__init__() 完成后, "that's it" is printed .

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

    >>> x = Third()
    second
    first
    that's it
    

    MRO算法已经从Python 2.3开始改进,在复杂的情况下运行良好,但我想在大多数情况下使用“深度优先从左到右遍历”“删除重复期望”仍然有效(请注释)如果不是这样的话) . 一定要阅读Guido的博客文章!

  • 11

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

    这里 A 是一个可扩展的基类, BC 是MixIn类,它们都提供函数 f . AB 都期望 __init__ 中的参数 vC 期望 w . 函数 f 接受一个参数 y . Q 继承自所有三个类 . 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)
    
  • 556
    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
    
  • 194

    关于@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 时他们覆盖一个方法,而它's one of few really useful and sane use of these '魔术变量' .

  • 14

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

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

  • 4

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

  • 17

    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
    

    Edit: 我看到有几个人认为上面的例子缺少 super() 调用,所以让我解释一下:这些例子的目的是展示如何构建MRO . 它们是 not 打算打印"first\nsecond\third"或其他什么 . 当然,您可以 - 当然应该使用该示例,添加 super() 调用,查看会发生什么,并深入了解Python的继承模型 . 但我的目标是保持简单并展示MRO是如何构建的 . 它按照我解释的那样构建:

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

    整体而言

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

    • 班上的孩子来到他们的父母面前

    • 左父母来到正确的父母面前

    • 课程仅在MRO中出现一次

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

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

    • 孩子

    • 家长

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

    每个方法都有超级最后一个

    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
    
  • 24

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

  • 2

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

    我明白这并没有直接回答 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__() 将不会被调用 .

  • 1

    我想在顶部添加what @Visionscaper says

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

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

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

    • C班

    • 加上合并

    • 其父母的线性化P1,P2,.. = L(P1,P2,...)和

    • 其父母P1,P2,...的列表

    线性化合并是通过选择显示为列表头部而不是尾部的公共类来完成的,因为订单很重要(将在下面变得清晰)

    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"
    

相关问题