一个python的super和mro问题今天让我纠结了一早上,源自于看tornado的源代码对于super这个“方法”产生的困惑,对于老鸟mro应该是常识了,对于小白而言,尼玛搞懂这个掉了我好几根头发。
谷歌了一下找到一篇博客也在讨论这个问题,博主列举了一个例子我觉得很典型,同时他也提出了和我一样的问题,我在原文的引用中高亮了。下面摘自“JohnsonGuo的专栏”:
有一天某同事设计了一个相对复杂的类体系结构(我们先不要管这个类体系设计得是否合理,仅把这个例子作为一个题目来研究就好),代码如代码段4:
代码段4:
class A(object):def __init__(self):print "enter A"print "leave A"
class B(object):def __init__(self):print "enter B"print "leave B"
class C(A):def __init__(self):print "enter C"super(C, self).__init__()print "leave C"
class D(A):def __init__(self):print "enter D"super(D, self).__init__()print "leave D"class E(B, C):def __init__(self):print "enter E"B.__init__(self)C.__init__(self)print "leave E"
class F(E, D):def __init__(self):print "enter F"E.__init__(self)D.__init__(self)print "leave F"
>>> f = F()
enter Fenter Eenter Bleave Benter Center Denter Aleave Aleave Dleave Cleave Eenter Denter Aleave Aleave Dleave F
明显地,类A和类D的初始化函数被重复调用了2次,这并不是我们所期望的结果!我们所期望的结果是最多只有类A的初始化函数被调用2次——其实这是多继承的类体系必须面对的问题。我们把代码段4的类体系画出来,如下图:
按我们对super的理解,从图中可以看出,在调用类C的初始化函数时,应该是调用类A的初始化函数,但事实上却调用了类D的初始化函数。好一个诡异的问题!
作者翻阅python源代码后得出的结论对我非常有帮助,同样摘抄如下,感谢JohnsonGuo的分享:
1. super并不是一个函数,是一个类名,形如super(B, self)事实上调用了super类的初始化函数,
产生了一个super对象;2. super类的初始化函数并没有做什么特殊的操作,只是简单记录了类类型和具体实例;3. super(B, self).func的调用并不是用于调用当前类的父类的func函数;4. Python的多继承类是通过mro的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数只调用一次(如果每个类都使用super);5. 混用super类和非绑定的函数是一个危险行为,这可能导致应该调用的父类函数没有调用或者一个父类函数被调用多次。
顺着作者的结论我想补充一条:
如作者所说super并不是像我们想象中一样直接找到当前类的父类,而是沿着mro顺藤摸瓜,所以有可能出现作者原文中提到的问题!
补充阅读:
对于想要了解更多细节而不愿意翻阅Python源码的人(比如我。。),强烈推荐阅读Python的这篇文档:“The Python 2.3 Method Resolution Order”,看下来试下来,基本就了解的八九不离十了。