在一般情况下,Python的super()实际如何工作? [英] How does Python's super() actually work, in the general case?

查看:91
本文介绍了在一般情况下,Python的super()实际如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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:

  1. 当前正在使用的环境是?如果没有,请创建一个包含队列的队列.获取class参数的MRO,将除第一个元素外的所有其他元素推入队列.
  2. 从当前上下文的MRO队列中弹出下一个元素,在构造super实例时将其用作当前类.
  3. super实例访问方法时,请在当前类中查找该方法,并使用相同的上下文进行调用.
  1. 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.
  2. Pop the next element from the current context's MRO queue, use it as the current class when constructing the super instance.
  3. 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.

这也介绍了许多设计注意事项.如果我只考虑BA的关系来编写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()显然会意识到之前的先前调用

super() is then obviously aware of the previous calls before it

不是.当您执行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

如果我只考虑B与A的关系写了B,然后后来别人写了C,而第三人写了D,我的B.foo()方法就必须以与C.foo()兼容的方式调用super. ),即使在我写它的时候它还不存在!

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!

没有期望您应该能够从任意类进行多重继承.除非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()实际如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆