问题:Python的super()如何与多重继承一起使用?
我在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)有关。
回答 0
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的构造方式。他们不打算打印“第一\第二\第三”或其他内容。您可以并且应该当然使用该示例,添加super()调用,看看会发生什么,并且对Python的继承模型有更深入的了解。但是我的目标是保持简单,并说明MRO的构建方式。正如我所解释的那样:
>>> Fourth.__mro__
(<class '__main__.Fourth'>,
<class '__main__.Second'>, <class '__main__.Third'>,
<class '__main__.First'>,
<type 'object'>)
回答 1
您的代码和其他答案都是错误的。他们错过了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
我想一点点地阐述一下答案,因为当我开始阅读如何在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"
根据 Guido van Rossum 撰写的有关方法解析顺序的文章,__init__在Python 2.3之前,使用“深度优先从左到右遍历”来计算解析顺序:
Third --> First --> object --> Second --> object
除去所有重复项(最后一个重复项除外)之后,我们得到:
Third --> First --> Second --> object
因此,让我们跟随实例化Third类实例时发生的情况x = Third()。
- 据MRO
Third.__init__执行。- 版画
Third(): entering - 然后
super(Third, self).__init__()执行,MRO返回First.__init__被调用。
- 版画
First.__init__执行。- 版画
First(): entering - 然后
super(First, self).__init__()执行,MRO返回Second.__init__被调用。
- 版画
Second.__init__执行。- 版画
Second(): entering - 然后
super(Second, self).__init__()执行,MRO返回object.__init__被调用。
- 版画
object.__init__执行(那里的代码中没有打印语句)- 执行返回到
Second.__init__随后打印Second(): exiting - 执行返回到
First.__init__随后打印First(): exiting - 执行返回到
Third.__init__随后打印Third(): exiting
这详细说明了为什么实例化Third()导致:
Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting
MRO算法已从Python 2.3开始进行了改进,可以在复杂的情况下很好地工作,但是我猜想在大多数情况下,仍然可以使用“深度优先的从左到右遍历” +“删除重复的期望值”(请如果不是这种情况,请发表评论)。请务必阅读Guido的博客文章!
回答 3
这就是所谓的Diamond问题,页面上有一个关于Python的条目,但是简而言之,Python将从左到右调用超类的方法。
回答 4
这就是我解决以下问题的方法:具有用于初始化的不同变量的多个继承以及具有相同函数调用的多个MixIn。我必须显式地将变量添加到传递的** kwargs中,并添加一个MixIn接口作为超级调用的端点。
这A是一个可扩展的基类,B并且C都是MixIn类,它们都提供功能f。 A并且B都v在他们的期望参数中__init__并C期望w。该函数f采用一个参数y。 Q从所有三个类继承。MixInF对于混合接口B和C。
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
我知道这并不能直接回答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__()不会被调用。
回答 6
总体
假设一切都源自object(如果不是),Python将基于您的类继承树计算方法解析顺序(MRO)。MRO满足3个属性:
- 班上的孩子比父母先
- 左父母先于右父母
- 一个Class只在MRO中出现一次
如果不存在此类排序,则Python错误。它的内部工作原理是该类祖先的C3 Linerization。在此处阅读所有内容:https://www.python.org/download/releases/2.3/mro/
因此,在下面的两个示例中,它是:
- 儿童
- 剩下
- 对
- 父母
调用方法时,该方法在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
关于@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)
结果:
A None
B hello
A1 6
B1 5
您也可以按位置使用它们:
B1(5, 6, b="hello", a=None)
但是您必须记住MRO,这确实令人困惑。
我可能会有点烦,但是我注意到人们忘记了每次使用的时间*args以及**kwargs他们重写方法时,虽然这是对这些“魔术变量”真正有用且明智的使用方法之一。
回答 8
另一个尚未涵盖的要点是传递用于初始化类的参数。由于的目标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
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 中定义的super init的调用将继续搜索MRO并查找Second中定义的init,对super的任何调用都将命中默认对象init。我希望这个例子可以澄清这个概念。
如果您不从First调用super。链停止,您将获得以下输出。
first 10
that's it
回答 10
在学习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
我想补充@Visionscaper在顶部所说的内容:
Third --> First --> object --> Second --> object
在这种情况下,解释器不会过滤掉对象类,因为它是重复的,而是因为第二类出现在层次结构子集中的头部位置而没有出现在尾部位置。而对象仅出现在尾部位置,在C3算法中不能将其视为确定优先级的强项。
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"
回答 12
在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:
第四->第三->第一->第二->基础->对象
回答 13
也许仍然可以添加一些东西,一个带有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)
其他方法也一样。
在具体类的继承列表中,我要添加LoginToPostbefore ListCreateAPIView和LoginToPutOrDeletebefore RetrieveUpdateDestroyAPIView。我的具体类get将保持不变。
回答 14
发布此答案供我将来参考。
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












