问题描述
super()
上有很多很棒的资源,包括 这篇很棒的博客文章,其中有很多,还有关于Stack Overflow的许多问题.但是,我觉得他们都没有解释最常见情况下的工作原理(带有任意继承图),以及幕后情况.
There are a lot of great resources on super()
, including this great blog post that pops up a lot, as well as many questions on Stack Overflow. However I feel like they all stop short of explaining how it works in the most general case (with arbitrary inheritance graphs), as well as what is going on under the hood.
考虑钻石继承的基本示例:
Consider this basic example of diamond inheritance:
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'
如果您从此 a>或查找维基百科页面进行C3线性化,您将看到MRO必须为(D, B, C, A, object)
.这当然由D.__mro__
确认:
If you read up on Python's rules for method resolution order from sources like this or look up the wikipedia page for C3 linearization, you will see that the MRO must be (D, B, C, A, object)
. This is of course confirmed by 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匹配的
.但是,请考虑在B
中的super(B, self).foo()
以上实际上调用了C.foo
,而在b = B(); b.foo()
中,它直接进入了A.foo
.显然,使用super(B, self).foo()
不仅是A.foo(self)
的快捷方式,有时还会讲授.
which matches the MRO. However, consider that above super(B, self).foo()
in B
actually calls C.foo
, whereas in b = B(); b.foo()
it would simply go straight to A.foo
. Clearly using super(B, self).foo()
is not simply a shortcut for A.foo(self)
as is sometimes taught.
super()
显然会意识到之前的先前调用以及该链正在尝试遵循的总体MRO.我可以看到有两种方法可以实现这一目标.第一种是做类似将super
对象本身作为self
参数传递到链中的下一个方法的操作,这将类似于原始对象,但也包含此信息.但是,这似乎也会破坏很多东西(super(D, d) is d
是错误的),通过做一些试验,我发现情况并非如此.
super()
is then obviously aware of the previous calls before it and the overall MRO the chain is trying to follow. I can see two ways this might be accomplished. The first is to do something like passing the super
object itself as the self
argument to the next method in the chain, which would act like the original object but also contain this information. However this also seems like it would break a lot of things (super(D, d) is d
is false) and by doing a little experimenting I can see this isn't the case.
另一种选择是具有某种全局上下文,该全局上下文存储MRO和其中的当前位置.我想象super
的算法类似于:
The other option is to have some sort of global context that stores the MRO and the current position in it. I imagine the algorithm for super
goes something like:
- 当前正在使用的环境是?如果没有,请创建一个包含队列的队列.获取class参数的MRO,将除第一个元素外的所有其他元素推入队列.
- 从当前上下文的MRO队列中弹出下一个元素,在构造
super
实例时将其用作当前类. - 从
super
实例访问方法时,请在当前类中查找该方法,并使用相同的上下文进行调用.
- Is there currently a context we are working in? If not, create one which contains a queue. Get the MRO for the class argument, push all elements except for the first into the queue.
- Pop the next element from the current context's MRO queue, use it as the current class when constructing the
super
instance. - When a method is accessed from the
super
instance, look it up in the current class and call it using the same context.
但是,这并不能解决奇怪的事情,例如使用不同的基类作为对super
的调用的第一个参数,甚至不能在其上调用其他方法.我想知道一般的算法.另外,如果此上下文存在于某处,我可以检查它吗?我可以把它弄糟吗?当然这是一个糟糕的主意,但是Python通常期望您成为一个成熟的成年人,即使您并非如此.
However, this doesn't account for weird things like using a different base class as the first argument to a call to super
, or even calling a different method on it. I would like to know the general algorithm for this. Also, if this context exists somewhere, can I inspect it? Can I muck with it? Terrible idea of course, but Python typically expects you to be a mature adult even if you're not.
这也介绍了许多设计注意事项.如果我只考虑B
与A
的关系来编写B
,则后来有人写了C
,而第三人写了D
,我的B.foo()
方法必须以兼容的方式调用super
使用C.foo()
,即使它在我写的时候还不存在!如果我希望我的班级易于扩展,我将需要考虑这一点,但是我不确定它是否比仅仅确保foo
的所有版本都具有相同的签名更为复杂.还有一个问题是,何时仅在调用super
之前或之后放置代码,即使仅考虑B
的基类也没有什么区别.
This also introduces a lot of design considerations. If I wrote B
thinking only of its relation to A
, then later someone else writes C
and a third person writes D
, my B.foo()
method has to call super
in a way that is compatible with C.foo()
even though it didn't exist at the time I wrote it! If I want my class to be easily extensible I will need to account for this, but I am not sure if it is more complicated than simply making sure all versions of foo
have identical signatures. There is also the question of when to put code before or after the call to super
, even if it does not make any difference considering B
's base classes only.
推荐答案
不是.当您执行super(B, self).foo
时,super
会知道MRO,因为那只是type(self).__mro__
,它知道它应该在B
之后的MRO中立即开始寻找foo
.粗略的纯Python等效项将是
It's not. When you do super(B, self).foo
, super
knows the MRO because that's just type(self).__mro__
, and it knows that it should start looking for foo
at the point in the MRO immediately after B
. A rough pure-Python equivalent would be
class super(object):
def __init__(self, klass, obj):
self.klass = klass
self.obj = obj
def __getattr__(self, attrname):
classes = iter(type(self.obj).__mro__)
# search the MRO to find self.klass
for klass in classes:
if klass is self.klass:
break
# start searching for attrname at the next class after self.klass
for klass in classes:
if attrname in klass.__dict__:
attr = klass.__dict__[attrname]
break
else:
raise AttributeError
# handle methods and other descriptors
try:
return attr.__get__(self.obj, type(self.obj))
except AttributeError:
return attr
没有期望您应该能够从任意类进行多重继承.除非foo
被专门设计为在多继承情况下被同级类重载,否则D不应该存在.
There's no expectation that you should be able to multiple-inherit from arbitrary classes. Unless foo
is specifically designed to be overloaded by sibling classes in a multiple-inheritance situation, D should not exist.
这篇关于在一般情况下,Python的super()实际如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!