关于 super()
,有很多优秀的资源可供使用,包括这篇非常出色的博客文章,以及Stack Overflow上的许多问题。然而,我感觉它们都没有充分解释它在最一般的情况下(具有任意继承图)是如何工作的,以及在幕后发生了什么。
考虑这个菱形继承的基本示例:
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中方法解析顺序的规则)或者查看维基百科上的 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
super()
会匹配MRO。但是,需要注意的是,在B
中,super(B, self).foo()
上方实际调用C.foo
,而在b = B(); b.foo()
中,它将直接转到A.foo
。显然,使用super(B, self).foo()
并不仅仅是一种快捷方式,就像有时候所教授的那样。
super()
显然会意识到它之前的调用以及整个MRO链正在尝试遵循的路径。我可以看到两种方法可以实现这一点。第一种方法类似于将super
对象本身作为下一个方法中的self
参数传递,这将像原始对象一样工作,但也包含此信息。然而,这似乎也会破坏很多东西(super(D, d) is d
为false),通过一些实验,我可以看到这不是事实。
另一个选择是具有存储MRO和其中当前位置的某种全局上下文。我想象super
的算法如下:
- 当前是否存在我们正在处理的上下文?如果没有,则创建一个包含队列的上下文。获取类参数的MRO,将除第一个元素外的所有元素推入队列中。
- 从当前上下文的MRO队列中弹出下一个元素,当构造
super
实例时,使用它作为当前类。 - 从
super
实例中访问方法时,在当前类中查找并使用相同的上下文调用它。
然而,这不能解释一些奇怪的事情,例如将不同的基类作为对super
的第一个参数的调用,甚至调用不同的方法。我想知道这个通用算法。此外,如果该上下文存在于某个位置,我能检查它吗?我能深入挖掘它吗?这当然是可怕的想法,但Python通常预计您是一个成熟的成年人,即使您不是。
这还引入了许多设计考虑因素。如果我编写B
时只考虑其与A
之间的关系,那么稍后有人编写了C
,第三个人编写了D
,我的B.foo()
方法必须以与C.foo()
兼容的方式调用super
,即使在我编写它时它不存在!如果我希望我的类易于扩展,我需要考虑这一点,但我不确定是否比仅确保所有版本的foo
具有相同的签名更为复杂。还有一个问题是何时在调用super
之前或之后放置代码,即使考虑到B
的基类可能不会造成任何影响。
*args, **kwargs
来清理传递的其他东西。super
的第一个参数是它应该查找方法的上层类 - 通常你想要当前类的直接上一级,因此使用super(ThisClass, self)
。"super(B, self).foo()
不仅仅是A.foo(self)
的快捷方式" - 它调用B
之后 MRO 中下一个foo
实现,并将其绑定到self
。你是否已经看了侧边栏中的相关问题,其中有相当多的问题与此相关。 - jonrsharpesuper(B, self).foo()
并不是A.foo(self)
的快捷方式,因为我自己更多地在学习它,但通常确实是这样教授的。当然,在单继承的大多数情况下,这并不重要,但这意味着一般情况下不应该使用带有显式参数的super(cls, self).__init__
,因为它最终会回到object
。 - JaredL