Python中的super

Posted on 2017-11-22 in Code

最近读了 laike9m 的一片文章,加上文章中提到的 Python’s super() considered super! 和 StackOverflow 上的一个答案,以及一些实践,简直是对super有了全新的认识。

首先,用 super 的直接好处是可以避免指定父类的名字,也就是说,如果有一天我们修改源码继承自另一个父类,super 引用依旧生效。比方说:

#  Previous
class LoggingDict(dict):
    def __setitem__(self, key, value):
        logging.info('Settingto %r' % (key, value))
        super(LoggingDict, self).__setitem__(key, value)

# Modified
class LoggingDict(SomeOtherMapping):
    def __setitem__(self, key, value):
        logging.info('Settingto %r' % (key, value))
        super(LoggingDict, self).__setitem__(key, value)

其次,super 并不是指向父类,而是指向 MRO 中的下一个类!

有关这一点,StackOverflow 上的答案解释的很清楚,super 其实就是在这件事:

def super(cls, inst):
    mro = inst.__class__.mro()
    return mro[mro.index(cls) + 1]

MRO 是 Method Resolution Order 的简写,代表了Python中类继承的顺序。 在这里,super 拿到 inst 的 MRO list,通过 cls 定位到它在当前 MRO 中的 index,并返回 MRO list 中的下一个。

借用 laike9m 的例子:

class Root(object):
    def __init__(self):
        print("this is Root")

class B(Root):
    def __init__(self):
        print("enter B")
        # print(self)  # this will print <__main__.D object at 0x...>
        super(B, self).__init__()
        print("leave B")

class C(Root):
    def __init__(self):
        print("enter C")
        super(C, self).__init__()
        print("leave C")

class D(B, C):
    pass

d = D()
print(d.__class__.__mro__)

输出

enter B
enter C
this is Root
leave C
leave B
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.Root'>, <type 'object'>)

为什么会有这样的结果呢?

首先我们可以从 d 的 MRO 中看出,D 类的继承顺序首先是 D 本身,然后是多重继承的 B 和 C,B、C 的父类 Root,最后到 object。

这个时候如果拿掉 B 类中 print(self) 的注释,重新跑一次脚本,你会发现这里的 self 依旧是 D 的实例,再结合之前提到的 super 真正在做的事情,当执行到 super(B, self).__init__() 的时候,super 找到了下一个类 C,而不是 B 的父类 Root。

最后,有关 MRO 可以查阅官方文档:The Python 2.3 Method Resolution Order,有一些关于 MRO 顺序的解释。