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

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"

super().__init__指的是Third的哪个父方法?我可以选择哪个运行吗?

我知道它与方法解析顺序(MRO)有关。

529884 次浏览

这被称为钻石问题,页面上有一个关于Python的条目,但简而言之,Python会从左到右调用超类的方法。

圭多本人在他的博客文章方法解析顺序(包括之前的两次尝试)中详细介绍了这一点。

在你的例子中,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 basesCannot 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'>)

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

输出:

>>> Third()secondfirstthird

super()调用在每一步找到MRO中的下一个方法,这就是为什么FirstSecond也必须拥有它,否则执行将在Second.__init__()结束时停止。


没有super()调用FirstSecond,输出缺少second

>>> Third()firstthird

我知道这并不能直接回答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__()不会被调用。

另一个尚未涵盖的点是为类的初始化传递参数。由于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, Adef __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__ 3C.__init__ with 1, 2A.__init__

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

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

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

这里A是一个可扩展的基类,BC是MixIn类,它们都提供函数fAB都在它们的__init__中期望参数vC期望w。函数f接受一个参数yQ从所有三个类继承。MixInFBC的混合接口。

class A(object):def __init__(self, v, *args, **kwargs):print "A:init:v[{0}]".format(v)kwargs['v']=vsuper(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']=vsuper(B, self).__init__(*args, **kwargs)self.v = vdef 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']=wsuper(C, self).__init__(*args, **kwargs)self.w = wdef 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)

我想详细说明一下答案是死气沉沉的,因为当我开始阅读如何在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编写,解决__init__的顺序是使用“深度优先从左到右遍历”计算的(在Python 2.3之前):

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

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

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

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

  1. 根据MROThird.__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__执行(代码中没有print语句)
  5. 执行返回到Second.__init__,然后打印Second(): exiting
  6. 执行返回到First.__init__,然后打印First(): exiting
  7. 执行返回到Third.__init__,然后打印Third(): exiting

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

Third(): enteringFirst(): enteringSecond(): enteringSecond(): exitingFirst(): exitingThird(): exiting

从Python 2.3开始,MRO算法已经得到了改进,可以在复杂的情况下很好地工作,但是我想使用“深度优先从左到右遍历”+“删除期望最后的重复项”在大多数情况下仍然有效(如果不是这种情况,请发表评论)。

class First(object):def __init__(self, a):print "first", asuper(First, self).__init__(20)
class Second(object):def __init__(self, a):print "second", asuper(Second, self).__init__()
class Third(First, Second):def __init__(self):super(Third, self).__init__(10)print "that's it"
t = Third()

输出是

first 10second 20that's it

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

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

first 10that's it

关于@小牛的评论,您可以像往常一样使用**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 NoneB helloA1 6B1 5

您也可以在位置上使用它们:

B1(5, 6, b="hello", a=None)

但是你必须记住MRO,它真的很混乱。你可以通过使用关键字参数来避免这种情况:

class A(object):def __init__(self, *args, a, **kwargs):print("A", a)

等等。

我可能有点烦人,但我注意到人们每次覆盖方法时都忘记使用*args**kwargs,而这是这些“神奇变量”的少数真正有用和理智的使用之一。

我想在顶部添加@Visionscpper怎么说

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

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

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

  • 的c
  • 加上合并
    • 其父母P1, P2,…=L(P1, P2,…)和
    • 其父母的列表P1,P2,…

线性化合并是通过选择显示为列表头部而不是尾部的公共类来完成的,因为顺序很重要(将在下面明确)

第三的线性化可以计算如下:

    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"

总体而言

假设一切都从object开始(如果不是,你只能靠自己),Python会根据你的类继承树计算方法解析顺序(MRO)。MRO满足3个属性:

  • 一个班级的孩子比他们的父母先来
  • 左父母先于右父母
  • 一个类在MRO中只出现一次

如果不存在这样的排序,则Python错误。它的内部工作原理是类祖先的C3线性化。在这里阅读所有关于它的信息:https://www.python.org/download/releases/2.3/mro/

当一个方法被调用时,该方法在MRO中的第一个出现是被调用的那个。任何未实现该方法的类都将被跳过。该方法中对super的任何调用都将调用该方法在MRO中的下一个出现。因此,你在继承中放置类的顺序以及你在方法中对super的调用都很重要。

请注意,您可以使用__mro__方法在python中查看MRO。

示例

以下所有示例都具有类的菱形继承,如下所示:

    Parent/   \/     \Left    Right\     /\   /Child

MRO是:

  1. 儿童
  2. 家长

您可以通过调用Child.__mro__来测试这一点,它返回:

(__main__.Child, __main__.Left, __main__.Right, __main__.Parent, object)

在每个方法中首先使用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()输出:

parentrightleftchild    

在每个方法中最后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()输出:

childleftrightparent

当不是所有的类都调用super

继承顺序最重要,如果不是继承调用super链中的所有类。例如,如果Left不调用超级,那么RightParent上的方法永远不会调用:

class Parent(object):def __init__(self):print("parent")super(Parent, self).__init__()
class Left(Parent):def __init__(self):print("left")
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()输出:

childleft

或者,如果Right没有调用superParent仍然会被跳过:

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")
class Child(Left, Right):def __init__(self):print("child")super(Child, self).__init__()

这里,Child()输出:

childleftright

在特定父级上调用方法

如果你想访问特定父类的方法,你应该直接引用该类,而不是使用超级。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):Parent.__init__(self)print("child")

在这种情况下,Child()输出:

parentchild

在learningpythonthehardway中,我学习了一个叫做超()的东西,如果没有弄错的话,这是一个内置函数。调用超()函数可以帮助继承通过父级和兄弟姐妹,帮助你看得更清楚。我仍然是一个初学者,但我喜欢分享我在python2.7中使用这个超()的经验。

如果你已经阅读了本页中的注释,你会听到方法解析顺序(MRO),方法是你写的函数,MRO将使用深度优先从左到右的方案来搜索和运行。你可以对此做更多的研究。

通过添加Super()函数

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

你可以通过添加其中的每个人和每个人来使用Super()连接多个实例和“家庭”。它将执行方法,浏览它们并确保你没有错过!然而,在之前或之后添加它们确实会产生影响,你将知道你是否完成了learningpythonthehardway练习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()

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

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 (beforethird (before)fourth (before)firstfourth (after)third (after)second (after)fifth (after)

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

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

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

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

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

from django.utils.decorators import method_decoratorfrom django.contrib.auth.decorators import login_required
class LoginToPost:@method_decorator(login_required)def post(self, arg, *args, **kwargs):super().post(arg, *args, **kwargs)

其他方法也是如此。

在我的具体类的继承列表中,我会在ListCreateAPIView之前添加我的LoginToPost,在RetrieveUpdateDestroyAPIView之前添加LoginToPutOrDelete。我的具体类get将保持未修饰。

在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(): enteringThird(): enteringFirst(): entering Will call Second nowSecond(): enteringBase(): enteringBase(): exitingSecond(): exitingFirst(): exitingThird(): exitingFourth(): exiting(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

如您所见,它以与被继承的顺序为每个继承的链调用foo一次。您可以通过调用.o先生来获取该顺序:

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

把这个答案贴出来供我将来参考。

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

    A/ \B   C\ /D

示例代码片段将是;-

class A:def __init__(self, name=None):#  this is the head of the diamond, no need to call super() hereself.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

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

案例1:单一继承

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

案例2:多重继承

在这里,在搜索Super(). Foo()实现时,层次结构中每个访问的类可能有也可能没有是一个关系。考虑以下示例:

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

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

在此处输入图片描述

(红色数字显示MRO)

MROI E C D A H F G B object

请注意,只有当继承自类X的所有子类都已被访问时,才会访问类X(即,您永远不应该访问从您尚未访问的下面的类中进入箭头的类)。

在这里,请注意,在访问类C之后,访问了D,尽管CD之间没有是一个关系(但两者都与A有关系)。这就是super()与单继承的不同之处。

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

在此处输入图片描述

(红色数字显示MRO)

MROI E C H D A F G B object

在这种情况下,我们从IE再到C。下一步是A,但我们还没有访问DA的一个子类。然而,我们不能访问D,因为我们还没有访问HD的一个子类。叶子H作为下一个要访问的类。记住,如果可能的话,我们试图在层次结构中向上,所以我们访问它最左边的超类D。在D之后,我们访问A,但我们不能向上访问对象,因为我们还没有访问E3、E4和E5。这些类依次完善了I的MRO。

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

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

资源学分:Richard L Halterman Python编程基础

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

class A():def __init__(self, x):self.x = x
class B():def __init__(self, y, z):self.y = yself.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)

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

  A    B\  /AB

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

示例:

class A():def __init__(self, a="a"):self.a = aprint(f"a={a}")    
def A_method(self):print(f"A_method: {self.a}")
class B():def __init__(self, b="b"):self.b = bprint(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

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

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