上有很多很棒的资源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
与 MRO 匹配。但是,请考虑上面super(B, self).foo()
inB
实际调用C.foo
,而 inb = B(); b.foo()
它会直接转到A.foo
。显然,使用super(B, self).foo()
不仅仅是A.foo(self)
有时被教导的捷径。
super()
然后显然知道之前的呼叫以及该链试图遵循的整体 MRO。我可以看到这可能实现的两种方式。第一种是做一些事情,比如将super
对象本身作为self
参数传递给链中的下一个方法,它的行为类似于原始对象,但也包含此信息。然而,这似乎也会破坏很多东西(super(D, d) is d
是错误的)并且通过做一些实验我可以看到情况并非如此。
另一种选择是使用某种全局上下文来存储 MRO 和其中的当前位置。我想算法super
类似于:
- 目前有我们正在工作的环境吗?如果没有,请创建一个包含队列的队列。获取类参数的 MRO,将除第一个元素之外的所有元素推入队列。
super
从当前上下文的 MRO 队列中弹出下一个元素,在构造实例时将其用作当前类。- 当从
super
实例访问方法时,在当前类中查找它并使用相同的上下文调用它。
但是,这并不能解释奇怪的事情,比如使用不同的基类作为调用的第一个参数super
,甚至调用不同的方法。我想知道这个的一般算法。另外,如果这个上下文存在于某个地方,我可以检查它吗?我可以玩弄它吗?当然,这个想法很糟糕,但 Python 通常希望你成为一个成熟的成年人,即使你不是。
这也引入了很多设计考虑。如果我写B
时只考虑它与 的关系A
,然后其他人写C
,第三人写D
,我的方法必须以兼容的方式B.foo()
调用,即使它在我写它的时候不存在!如果我希望我的类易于扩展,我需要考虑这一点,但我不确定它是否比简单地确保所有版本具有相同的签名更复杂。还有一个问题是何时在调用 之前或之后放置代码,即使仅考虑 的基类并没有任何区别。super
C.foo()
foo
super
B