在一般情况下,Python的super()实际上是如何工作的?

Jar*_*edL 3 python oop multiple-inheritance super method-resolution-order

有很多很棒的资源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'
Run Code Online (Sandbox Code Playgroud)

如果从像源Python的规则,方法解析顺序读了这个或查找的维基百科页面的C3线性化,你会看到,MRO必须(D, B, C, A, object).这当然得到以下证实D.__mro__:

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
Run Code Online (Sandbox Code Playgroud)

d = D()
d.foo()
Run Code Online (Sandbox Code Playgroud)

版画

D foo before
B foo before
C foo before
A foo
C foo after
B foo after
D foo after
Run Code Online (Sandbox Code Playgroud)

哪个与MRO相匹配.但是,super(B, self).foo()B实际调用中考虑上述内容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是这样的:

  1. 目前我们正在开展工作吗?如果没有,请创建一个包含队列的.获取类参数的MRO,将除第一个元素之外的所有元素推入队列.
  2. 从当前上下文的MRO队列中弹出下一个元素,在构造super实例时将其用作当前类.
  3. super实例访问方法时,在当前类中查找它并使用相同的上下文调用它.

但是,这并没有考虑到使用不同的基类作为调用的第一个参数super,甚至在其上调用不同的方法等奇怪的事情.我想知道这个的一般算法.此外,如果这种情况存在于某处,我可以检查它吗?我可以捣蛋吗?当然可怕的想法,但Python通常希望你成为一个成熟的成年人,即使你不是.

这也引入了许多设计考虑因素.如果我B只想到它的关系A,那么后来有人写C,第三个人写D,我的B.foo()方法必须以super一种兼容的方式调用,C.foo()即使它在我写它的时候不存在!如果我希望我的类可以轻松扩展,我将需要考虑到这一点,但我不确定它是否比简单地确保所有版本foo具有相同签名更复杂.还有一个问题是何时在调用之前或之后放置代码super,即使它只考虑B基类而没有任何区别.

use*_*ica 8

然后super()显然知道之前的调用

不是.当你这样做的时候super(B, self).foo,super知道MRO,因为那只是type(self).__mro__,而且它知道它应该立即开始foo在MRO中寻找B.一个粗略的纯Python等价物

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
Run Code Online (Sandbox Code Playgroud)

如果我写B只考虑它与A的关系,那么后来有人写C并且第三个人写D,我的B.foo()方法必须以与C.foo()兼容的方式调用super,即使它在我写的时候不存在!

没有任何期望你应该能够从任意类中多次继承.除非foo专门设计为在多继承情况下由兄弟类重载,否则D不应存在.