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

Cal*_*sto 825 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"
Run Code Online (Sandbox Code Playgroud)

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

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

rbp*_*rbp 660

Guido本人在他的博客文章解决方案顺序(包括两个早期的尝试)中详细说明了这一点.

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

class Third(First, Second):
    ...
Run Code Online (Sandbox Code Playgroud)

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"
Run Code Online (Sandbox Code Playgroud)

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"
Run Code Online (Sandbox Code Playgroud)

应该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
Run Code Online (Sandbox Code Playgroud)

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

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)
Run Code Online (Sandbox Code Playgroud)

  • 我认为在第一堂课中缺少超级电话是这个答案的一个非常大的问题; 没有讨论如何/为什么对问题的重要批判性理解失去了. (49认同)
  • 当你开始在First,Second和Third [http://pastebin.com/ezTyZ5Wa]中调用super()时,它变得更有趣(并且可以说更令人困惑). (10认同)
  • @Cerin这个例子的目的是展示如何构建MRO.该示例不打算打印"first \nsecond\third"或其他任何内容.MRO确实是正确的:第四.__ mro__ ==(<class'__main __.Fourth'>,<class'__main __.Second'>,<class'__main __.Third'>,<class'__ main __.First'>,<输入'object'>) (6认同)
  • 据我所知,这个答案缺少 OP 的一个问题,即“如果你想运行另一个怎么办?”。我想看看这个问题的答案。我们是否应该明确地命名基类? (5认同)
  • 这个答案完全错了.没有父母的super()调用,什么都不会发生.@lessless的回答是正确的. (3认同)

lif*_*ess 233

你的代码和其他答案都是错误的.他们缺少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")
Run Code Online (Sandbox Code Playgroud)

super()呼叫发现在每一个步骤,这就是为什么第一和第二必须有它太MRO下一个方法,否则停止执行结束Second.__init__().

这就是我得到的:

>>> Third()
second
first
third
Run Code Online (Sandbox Code Playgroud)

  • 如果这些类需要不同的参数来初始化自己该怎么办? (77认同)
  • *multiple*继承的设计在python中真的很糟糕.基类*几乎*需要知道谁将派生它,以及派生将派生多少其他基类,以及以什么顺序...否则`super`将无法运行(因为参数不匹配),或者它不会调用几个基础(因为你没有在其中一个打破链接的基础上写`super`)! (12认同)
  • 这样,两个BOTH基类的__init__方法将被执行,而原始示例只调用MRO中遇到的第一个__init__.我猜这是"合作子类化"一词所暗示的,但澄清本来是有用的('明确比隐含更好',你知道;)) (4认同)
  • "合作子类化" (2认同)
  • 是的,如果您将不同的参数传递给通过 super 调用的方法,则该方法的所有实现都需要通过 MRO 向 object() 提供兼容的签名。这可以通过关键字参数来实现:接受比方法使用的更多的参数,并忽略额外的参数。这样做通常被认为是丑陋的,并且在大多数情况下添加新方法会更好,但是 __init__ 作为一个特殊的方法名称(几乎?)是唯一的,但具有用户定义的参数。 (2认同)
  • 只需使用类名显式调用构造函数,不要使用 super 以避免此类晦涩的代码。 (2认同)
  • @lifeless 是的,您将多次运行基类。但我认为这比在所有类中调用 super 更好,以防万一你的类被另一个调用 super 的类继承。如果您的某个类继承自一个不调用 super 的内置类怎么办?也许我们可以围绕内置类构建一个包装器,但对我来说,似乎 super 确实很容易出错,而且通常很难维护,并且当项目规模扩大时,可能会带来大量不必要的编码。 (2认同)

Vis*_*per 166

我想通过一点点没有详细解释答案,因为当我开始阅读如何在Python中的多继承层次结构中使用super()时,我没有立即得到它.

您需要了解的是,根据在完整继承层次结构的上下文中使用的方法分辨率排序(MRO)算法super(MyClass, self).__init__()提供下一个 __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"
Run Code Online (Sandbox Code Playgroud)

根据 Guido van Rossum 关于方法解决顺序这篇文章,__init__使用"深度优先从左到右遍历"计算要解决的顺序(在Python 2.3之前):

Third --> First --> object --> Second --> object
Run Code Online (Sandbox Code Playgroud)

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

Third --> First --> Second --> object
Run Code Online (Sandbox Code Playgroud)

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

  1. 根据MRO Third.__init__of Third称为第一名.

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

  3. 里面First.__init__首先First(): entering调用super(First, self).__init__()第二个,因为那是MRO决定的!

  4. Second.__init__第二个内部Second.__init__调用Second(): entering对象,这无关紧要.之后 打印"第二个".

  5. super(Second, self).__init__()完成, "第一"被打印.

  6. object.__init__完成,"就是这样"被打印.

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

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting
Run Code Online (Sandbox Code Playgroud)

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

  • 我认为第3步需要更多的解释:如果`Third`没有从'Second`继承,那么`super(First,self).__ init__`将调用`object .__ init__`并且在返回之后,将打印"first".但是因为`Third`继承了'First`和`Second`,而不是在`First .__ init__`之后调用`object .__ init__`,MRO规定只保留对`.__ init__`的最终调用,并且打印在`object .__ init__`返回之前,不会达到`First`和`Second`中的语句.由于`Second`是最后一个调用`object .__ init__`,它在`Second`之前返回` (14认同)
  • 我仍然不明白为什么:在第一个超级(第一个,自我)的内部__init__ .__ init __()调用第二个__init__,因为那是MRO的指示! (3认同)

mon*_*res 51

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

  • `object`是第四个 (138认同)
  • 这不是钻石问题。钻石问题涉及四类,而 OP 的问题只涉及三类。 (3认同)
  • 这根本不是真正的钻石问题,因为没有传递共享基类(除了“object”,但这是所有类的公共基类,并且在_this_问题中不起作用)。Python 调用方法的确切顺序*不*那么简单,[类层次结构的 C3 线性化](https://en.wikipedia.org/wiki/C3_线性化)可能会导致非常不同的顺序。 (2认同)

bre*_*yne 25

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

A是一个可扩展的基类,B并且C是提供功能的MixIn类f. AB这两个参数期望v在他们__init__C期待w.该函数f有一个参数y. Q继承自所有三个类.MixInF对于混合接口BC.


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)
Run Code Online (Sandbox Code Playgroud)

  • 从理论上讲,是的.实际上,每次我在python中遇到Diamond继承时都会出现这种情况,所以我在这里添加了它.因为,每次我不能干净地避免钻石继承,这就是我去的地方.以下是未来我的一些额外链接:http://rhettinger.wordpress.com/2011/05/26/super-considered-super/ http://code.activestate.com/recipes/577721-how-to-use -Super-有效的Python-27版/ (6认同)

Sea*_*aux 21

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

  • 它不会被调用,因为你没有调用每个继承的类.问题是,如果`First`和`Second`都继承另一个类并直接调用它,那么这个公共类(钻石的起点)被调用两次.超级是避免这种情况. (4认同)

Zag*_*ags 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"
Run Code Online (Sandbox Code Playgroud)

Child() 输出:

parent
right
left
child
Run Code Online (Sandbox Code Playgroud)

随着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__()
Run Code Online (Sandbox Code Playgroud)

Child() 输出:

child
left
right
parent
Run Code Online (Sandbox Code Playgroud)

  • @ alpha_989如果只想访问特定类的方法,则应该直接引用该类,而不是使用super.Super是关于继承链,而不是特定类的方法. (4认同)

Mar*_*lla 16

关于@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)
Run Code Online (Sandbox Code Playgroud)

结果:

A None
B hello
A1 6
B1 5
Run Code Online (Sandbox Code Playgroud)

您也可以按位置使用它们:

B1(5, 6, b="hello", a=None)
Run Code Online (Sandbox Code Playgroud)

但你必须记住MRO,这真的令人困惑.

我可能有点讨厌,但是我注意到人们忘记了每次使用它们*args并且**kwargs当它们覆盖一个方法时,它是少数真正有用且理智的使用这些"魔术变量"之一.

  • 哇,这真的很难看。遗憾的是你不能只说出你想调用哪个特定的超类。尽管如此,这还是让我更有动力使用组合并避免像瘟疫一样的多重继承。 (2认同)

小智 15

如果您尝试继承的每个类都有自己的 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)
Run Code Online (Sandbox Code Playgroud)


Tri*_*ion 12

另一个尚未涉及的问题是传递用于初始化类的参数.由于目标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()
Run Code Online (Sandbox Code Playgroud)

得到:

[<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__
Run Code Online (Sandbox Code Playgroud)

__init__直接调用超类来更直接地分配参数是诱人的,但是如果super超类中有任何调用和/或MRO被更改并且类A可能被多次调用,则取决于实现,将失败.

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


D.D*_*iso 9

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

  A    B
   \  /
    AB
Run Code Online (Sandbox Code Playgroud)

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


A()
>>> a=a

B()
>>> b=b

AB()
>>> a=A
>>> b=B
>>> A_method: A
>>> B_method: B
Run Code Online (Sandbox Code Playgroud)

为了证明两个父母被合并到孩子中,请考虑magical_AB_method在 class 内定义B。当从 的实例调用时B,该方法会失败,因为它无权访问内部的成员变量A。但是,当从 child 的实例调用时AB,此方法可以工作,因为它继承了所需的成员变量A

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

AB().magical_AB_method()
>>> magical_AB_method: A, B
Run Code Online (Sandbox Code Playgroud)


T.M*_*M15 8

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

案例 1:单一继承

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

案例 2:多重继承

在这里,同时寻找超()。美孚()实现,在层次结构可能会或可能不会有参观每类是一个关系。考虑以下示例:

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
Run Code Online (Sandbox Code Playgroud)

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

在此处输入图片说明

(红色数字显示 MRO)

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

请注意,X 只有当从它继承的所有子类都被访问过时,才会访问一个类(即,您永远不应该访问一个类,该类的箭头来自您尚未访问过的下面的类) .

在这里,请注意,在访问 class 之后CDisvisitedCDDO NOT have它们之间关系(但两者都有 with A)。这是super()与单继承不同的地方。

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

在此处输入图片说明

(红色数字显示 MRO)

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

在这种情况下,我们从IEC。下一步将是A,但我们还没有访问DA. D但是,我们不能访问,因为我们还没有访问H, 的子类D。叶子H作为下一个要参观的班级。请记住,如果可能,我们会尝试在层次结构中向上移动,因此我们访问其最左侧的超类D. 之后D我们参观A,但我们不能上去对象,因为我们还没有访问FGB。这些类依次完善了I.

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

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

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


rfe*_*rov 6

在 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__)
Run Code Online (Sandbox Code Playgroud)

输出:

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'>)
Run Code Online (Sandbox Code Playgroud)

如您所见,对于每个继承的链,它以与继承相同的顺序调用 foo 一次。您可以通过调用获取该订单先生

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


Ser*_*mad 5

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()
Run Code Online (Sandbox Code Playgroud)

输出是

first 10
second 20
that's it
Run Code Online (Sandbox Code Playgroud)

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

如果你不从 First 调用 super。链停止,您将获得以下输出。

first 10
that's it
Run Code Online (Sandbox Code Playgroud)

  • 那是为了说明调用顺序 (2认同)