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)
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)
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().
根据MRO Third.__init__of Third称为第一名.
接下来,根据MRO,Third(): entering方法内部super(Third, self).__init__()解析为First.__init__First 的方法,该方法被调用.
里面First.__init__首先First(): entering调用super(First, self).__init__()的第二个,因为那是MRO决定的!
在Second.__init__第二个内部Second.__init__调用Second(): entering对象,这无关紧要.之后
打印"第二个".
后super(Second, self).__init__()完成,
"第一"被打印.
后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的博客文章!
bre*_*yne 25
这是我如何解决如何使用不同变量进行多重继承以进行初始化以及具有多个具有相同函数调用的MixIns的问题.我必须显式添加变量来传递**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)
Run Code Online (Sandbox Code Playgroud)
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__()不会被调用.
Zag*_*ags 19
假设一切都来自object(如果不是你自己就是你自己),Python会根据你的类继承树计算方法解析顺序(MRO).MRO满足3个属性:
如果不存在这样的排序,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"
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)
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当它们覆盖一个方法时,它是少数真正有用且理智的使用这些"魔术变量"之一.
小智 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可能被多次调用,则取决于实现,将失败.
总结:协作继承和用于初始化的超级和特定参数不能很好地协同工作.
考虑 child AB,其中parentsA和B在其构造函数中具有关键字参数。
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)
考虑super().Foo()从子类调用。该方法解析顺序(MRO)方法是在方法调用解决的顺序。
在这种情况下, super().Foo() 将在层次结构中向上搜索并考虑最接近的实现,如果找到,则引发异常。在任何访问过的子类与其在层次结构中的超类之间,“是一个”关系将始终为真。但是这个故事在多重继承中并不总是一样的。
在这里,同时寻找超()。美孚()实现,在层次结构可能会或可能不会有参观每类是一个关系。考虑以下示例:
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 之后C,DisvisitedC和DDO NOT have是它们之间的关系(但两者都有 with A)。这是super()与单继承不同的地方。
考虑一个稍微复杂的例子:
(红色数字显示 MRO)
MRO是 I E C H D A F G B object
在这种情况下,我们从I到E到C。下一步将是A,但我们还没有访问D,A. D但是,我们不能访问,因为我们还没有访问H, 的子类D。叶子H作为下一个要参观的班级。请记住,如果可能,我们会尝试在层次结构中向上移动,因此我们访问其最左侧的超类D. 之后D我们参观A,但我们不能上去对象,因为我们还没有访问F,G和B。这些类依次完善了I.
请注意,任何类都不能在 MRO 中出现多次。
这就是 super() 在继承层次结构中的查找方式。
资源来源:Richard L Halterman Python 编程基础
在 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 一次。您可以通过调用获取该订单。先生:
第四 -> 第三 -> 第一 -> 第二 -> 基础 -> 对象
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)