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

时间:2015-10-22 21:15:56

标签: python oop multiple-inheritance super method-resolution-order

super()上有很多很棒的资源,包括this很棒的博客帖子,以及关于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'

如果您从this等来源阅读Python的方法解析顺序规则,或者查找wikipedia page进行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()B以上C.foo实际调用b = B(); b.foo(),而在A.foo中,它只会直接转到super(B, self).foo()。显然,使用A.foo(self)并不仅仅是super()的捷径,有时会被教导。

super显然已经意识到之前的呼叫以及链条试图遵循的整体MRO。我可以看到两种方法可以完成。第一种方法是将self对象本身作为super(D, d) is d参数传递给链中的下一个方法,这将像原始对象一样,但也包含此信息。然而,这似乎也会破坏很多事情(super是假的),通过做一些实验我可以看到情况并非如此。

另一种选择是拥有某种全局上下文来存储MRO及其当前位置。我想super的算法类似于:

  1. 目前我们正在开展工作吗?如果没有,请创建一个包含队列的。获取类参数的MRO,将除第一个元素之外的所有元素推送到队列中。
  2. 从当前上下文的MRO队列中弹出下一个元素,在构造super实例时将其用作当前类。
  3. super实例访问方法时,请在当前类中查找并使用相同的上下文调用它。
  4. 但是,这并不能解释奇怪的事情,例如使用不同的基类作为调用B的第一个参数,或者甚至在其上调用不同的方法。我想知道这个的一般算法。此外,如果这种情况存在于某处,我可以检查它吗?我可以捣蛋吗?当然可怕的想法,但Python通常希望你成为一个成熟的成年人,即使你不是。

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

1 个答案:

答案 0 :(得分:8)

  然后

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

不是。当您执行super(B, self).foo时,super知道MRO,因为它只是type(self).__mro__,而且它知道它应该在{{{I}之后的MRO点开始寻找foo 1}}。粗略的纯Python等价物将是

B
  

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

没有任何期望你应该能够从任意类中多次继承。除非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 专门设计为在多继承情况下被兄弟类重载,否则D不应该存在。

相关问题