super()
有很多很棒的资源,包括this很棒的博客文章,弹出很多,以及关于堆栈溢出的很多问题。不过,我觉得他们都停下来解释它在最常见的情况下如何工作(使用任意的继承图),以及引擎盖下正在发生的事情。在一般情况下,Python的super()实际上是如何工作的?
考虑菱形继承的这个简单的例子:
class A(object):
def foo(self):
print 'A foo'
class B(A):
def foo(self):
print 'B foo before'
super(B, self).foo()
print 'B foo after'
class C(A):
def foo(self):
print 'C foo before'
super(C, self).foo()
print 'C foo after'
class D(B, C):
def foo(self):
print 'D foo before'
super(D, self).foo()
print 'D foo after'
如果您对Python的规则从源方法解析顺序读取像this或查找的wikipedia page为C3线性化,你会看到MRO必须是(D, B, C, A, object)
。这当然是由D.__mro__
证实:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
而且
d = D()
d.foo()
打印
D foo before
B foo before
C foo before
A foo
C foo after
B foo after
D foo after
其MRO匹配。但是,考虑到以上的super(B, self).foo()
实际上称为C.foo
,而在b = B(); b.foo()
中,它将直接转到A.foo
。正如有时教导的那样,清楚地使用super(B, self).foo()
不仅仅是A.foo(self)
的简写。
super()
然后很明显知道之前的调用以及该链试图遵循的整体MRO。我可以看到两种方式可以实现。第一种方法是将super
对象本身作为self
参数传递给链中的下一个方法,它将像原始对象那样工作,但也包含此信息。但是,这似乎也会破坏很多东西(super(D, d) is d
是错误的),并且通过做一点实验我可以看出情况并非如此。
另一种选择是有某种全局上下文来存储MRO和其中的当前位置。我想super
的算法如下所示:
- 目前是否有我们正在使用的上下文?如果没有,则创建一个包含队列的文件。获取类参数的MRO,将除第一个元素之外的所有元素都推送到队列中。
- 从当前上下文的MRO队列中弹出下一个元素,在构造
super
实例时将其用作当前类。 - 当从
super
实例访问某个方法时,请在当前类中查找并使用相同的上下文调用它。
然而,这并不能解释像使用不同的基类作为第一个参数来调用super
,甚至调用它不同的方法奇怪的事情。我想知道这一般的算法。另外,如果这个上下文存在,我可以检查它吗?我可以使用它吗?可怕的想法,但Python通常希望你成为一个成熟的成年人,即使你不是。
这也引入了很多设计考虑因素。如果我写B
只考虑它与A
的关系,稍后有人写C
而第三个人写D
,我的B.foo()
方法必须以与C.foo()
兼容的方式呼叫super
,即使它在当时不存在我写的!如果我希望我的课程易于扩展,我将需要对此进行说明,但我不确定它是否比仅确保foo
的所有版本具有相同签名更加复杂。在super
的调用之前或之后,即使它仅考虑B
的基类没有任何区别,也存在何时在代码之前或之后放置代码的问题。
“我不确定它是否比简单地确保foo的所有版本都具有相同的签名更复杂”,这是超级普遍使用的要求(尽管您可以使用kwargs来解决它) –
您需要相同的签名或使用'* args,** kwargs'来扫描任何正在传播的东西。 “super”的第一个参数是*上面的类*它应该寻找方法 - 通常你想要一个在当前的那个之上,因此'super(ThisClass,self)'。 *“'super(B,self).foo()'不是简单的'A.foo(self)'”* - no的快捷方式,它会在'B'后面的MRO中调用下一个'foo'实现,绑定它“自我”。您是否看过侧边栏中的**相关**问题,有相当多的相关问题。 – jonrsharpe
@jonrsharpe - 我已经看过相关的问题,但正如我所说,他们真的只处理简化版本,而不是一般情况。我最近了解到'super(B,self).foo()'不是'A.foo(self)'的快捷方式,因为我一直在对自己进行更多的介绍,但实际上这种方法通常是这样教的。当然,在绝大多数单一继承的情况下,这并不重要,但它通常意味着使用具有明确参数的'super(cls,self).__ init__'是不适当的,因为它可以回到'对象“。 – JaredL