Python 类继承调用顺序



有一个著名的Python示例

class A(object):
def go(self):
print("go A go!")
class B(A):
def go(self):
super(B, self).go()
print("go B go!")
class C(A):
def go(self):
super(C, self).go()
print("go C go!")
class D(B,C):
def go(self):
super(D, self).go()
print("go D go!")

d = D()
d.go() 
#go A go!
#go C go!
#go B go!
#go D go!

我有几个问题。第一个是 B 调用 A,C 调用 A,所以我希望 A 出现两次。第二个问题是关于顺序的。

从 Python 2.3 开始,方法解析使用了一种称为 C3 线性化的算法(借用自 Dylan)。维基百科有一篇很好的文章。

顾名思义,这个想法是强制方法解析图是一条直线,即使继承图不是。这意味着A在设计上不会出现两次。

为什么?嗯,一方面,它完全避免了困扰许多其他语言多重继承的"钻石问题"。(或者更准确地说,许多其他语言要么禁止MI,要么将其限制为纯粹的"接口",因为他们没有解决C++中存在的问题。

最初的 Python 解释(包括其背后的动机)可在 Python 2.3 方法解析顺序中找到。这有点技术性,但如果您有兴趣,值得一读。

您可能还想阅读原始的Dylan论文,其中更详细地介绍了非线性MRO图的问题,以及提出单调线性化的挑战(即,它按照您期望的顺序进行,或者至少是您在克服线性事实后所期望的顺序), 等等。

如果你想更深入地了解type()是如何工作的,或者只是想看看 2.3 和 3.7 之间有什么变化(例如,__mro__创建和更新的方式——尽管 magic 3.xsuper在其他地方),真的没有比 CPython 源代码更好的地方了。

super不只是恢复超类。它实例化一个对象,该对象在给定方法解析顺序的上下文中恢复方法。每个类都有一个 mro,您可以通过__mro__属性访问该mro

D.__mro__ # (D, B, C, A, object)

因此,当给定一个类和一个实例时,super首先从该实例中恢复mro。当您尝试从super对象恢复属性时,它会从具有此类属性的提供的类之后的第一个类返回该属性。

如果你要在 Python 中实现super的行为,它看起来像这样。

class super:
def __init__(self, cls, instance):
if not isinstance(cls, type):
raise TypeError('super() argument 1 must be type')
if isinstance(instance, cls):
self.mro = type(instance).__mro__
elif isinstance(instance, type) and issubclass(instance, cls):
self.mro = instance.__mro__
else:
raise TypeError('super(type, obj): obj must be an instance or subtype of type')
self.cls = cls
self.instance = instance
def __getattr__(self, attr):
cls_index = self.mro.index(self.cls)
for supercls in self.mro[cls_index + 1:]:
if hasattr(supercls, attr): break
# The actual implementation binds instances to methods before returning
return getattr(supercls, attr)

所以回到你的例子,当你调用super(B, self).go时,它会恢复self__mro__,它是D类型。然后,它从具有此类属性的mroB之后的第一个类中选取go

所以在这种情况下,由于self.__mro__(D, B, C, A, object)B后面的第一个类具有属性goC而不是A

如果你想了解Python如何确定mro的细节,那么我建议abarnert的答案。

最新更新