Python MRO & super

MRO

Method resolution order是python用来解析方法调用顺序的。MRO对于多重继承中方法调用异常重要。python中有一个内建函数和MRO密切相关——super。顾名思义,super看上去应该是调用父类的方法,通常情况下也是如此。来看一段代码:

[python]  view plain copy print ?
  1. class A(object):  
  2.     def __init__(self):  
  3.         print 'A.__init__'  
  4. class B(A):  
  5.     def __init__(self):  
  6.         print 'B.__init__'  
  7.         # try to call parent's __init__ without explicitly reference class A  
  8.         super(B, self).__init__()  
  9. >>> x = B()  
  10. B.__init__  
  11. A.__init__  

这里我们通过super来调用父类的__init__,super(B, self)返回一个bounded对象(因为我们传入了self)。

从输出可以看到,调用正确。就像我们直接调用A.__init__(self)一样。

这样做的好处是,可以不用直接引用基类的名称就可以调用基类的方法。如果我们改变了基类的名称,那么所有子类的调用将不用改变。

但是super其实并不是我们想的那么简单,super不是简单地调用所谓基类的方法,而是调用MRO中的下一个类的方法,也就是类似于next的方法。

[python]  view plain copy print ?
  1. # 这段代码摘自Python's Super Considered Harmful  
  2. class A(object):  
  3.     def __init__(self):  
  4.         print "A"  
  5.         super(A, self).__init__()  
  6. class B(object):  
  7.     def __init__(self):  
  8.         print "B"  
  9.         super(B, self).__init__()  
  10. class C(A):  
  11.     def __init__(self, arg):  
  12.         print "C","arg=",arg  
  13.         super(C, self).__init__()  
  14. class D(B):  
  15.     def __init__(self, arg):  
  16.         print "D""arg=",arg  
  17.         super(D, self).__init__()  
  18. class E(C,D):  
  19.     def __init__(self, arg):  
  20.         print "E""arg=",arg  
  21.         super(E, self).__init__(arg)  
  22. #print "MRO:", [x.__name__ for x in E.__mro__]  
  23. E(10)  

对于这段代码,我们可能期望输出像这样:

[python]  view plain copy print ?
  1. E arg= 10  
  2. C arg= 10  
  3. A  
  4. D arg= 10  
  5. B  

但事实上,这段代码会引发错误,因为python没有像我们想的那样调用正确的函数。

[python]  view plain copy print ?
  1. E arg= 10  
  2. C arg= 10  
  3. A  
  4. Traceback (most recent call last):  
  5.   File "C:/Users/Administrator/Desktop/example1-2.py", line 27in <module>  
  6.     E(10)  
  7.   File "C:/Users/Administrator/Desktop/example1-2.py", line 24in __init__  
  8.     super(E, self).__init__(arg)  
  9.   File "C:/Users/Administrator/Desktop/example1-2.py", line 14in __init__  
  10.     super(C, self).__init__()  
  11.   File "C:/Users/Administrator/Desktop/example1-2.py", line 4in __init__  
  12.     super(A, self).__init__()  
  13. TypeError: __init__() takes exactly 2 arguments (1 given)  

我们先给出上面的代码中注释掉的输出mro的语句的输出:

[python]  view plain copy print ?
  1. MRO: ['E''C''A''D''B''object']  

出错的原因是因为调用继续到A.__init__时,我们调用了super(A,self).__init__。记得上面我们说过super类似于next函数,是调用mro中下一个类型的方法。

这里我们给出的类型是A,那么mro中下一个类型就是D,很显然,super将会调用D.__init__(self)。可是,D.__init__却接受一个额外的参数arg,所以调用错误。

super并不像它的名字那样,只调用父类的方法,而是调用MRO中,下一个类型的方法。

reference中的链接中给出了使用super的建议,可以作为参考。

 

Summary

  1. 如果类被设计成使用了super,那么所有子类也必须要调用super,否则直接调用会出现重复调用的问题
  2. super调用的目标函数通常是用 *args, **kwargs 作为参数,这样可以解决目标函数参数匹配的问题

MRO Implementation

这个mro是根据The Python 2.3 Method Resolution Order中的描述,自己写出来的。该paper中也有相关的实现,而且更加精巧。

[python]  view plain copy print ?
  1. import inspect  
  2. def compute_linearization(kls):  
  3.     """ 
  4.     Given a class object, calculate the mro of the class 
  5.     A linerization is defined as the class plus the merge of the linerization of all bases and the list of bases 
  6.     """  
  7.     if inspect.isclass(kls):  
  8.         mro = [kls]  
  9.         # for each base class, we need to compute the linerization  
  10.         merge_list = []  
  11.         for basekls in kls.__bases__:  
  12.             merge_list.append(compute_linearization(basekls))  
  13.         # add all bases to the merge list  
  14.         merge_list.append([])  
  15.         for basekls in kls.__bases__:  
  16.             merge_list[-1].append(basekls)  
  17.         return mro + merge(merge_list)  
  18.     else:  
  19.         raise TypeError("argument must a class object")  
  20. """ 
  21. take the head of the first list, i.e L[B1][0]; if this head is not in the tail of any of the other lists, then add it to the linearization of C and remove it from the lists in the merge, otherwise look at the head of the next list and take it, if it is a good head. Then repeat the operation until all the class are removed or it is impossible to find good heads. In this case, it is impossible to construct the merge, Python 2.3 will refuse to create the class C and will raise an exception. 
  22. """  
  23. def merge(merge_list):  
  24.     res = []  
  25.     while True:  
  26.         processed = False  
  27.         has_good_head = False  
  28.         for i, l in enumerate(merge_list):  
  29.             if len(l):  
  30.                 # mark for processing  
  31.                 processed = True  
  32.                 head = l[0]  
  33.                 is_good_head = True  
  34.                 other_lists = merge_list[0:i] + merge_list[i+1:]  
  35.                 # check if the head is in the tail of other lists   
  36.                 for rest in other_lists:  
  37.                     if head in rest[1:]:  
  38.                         is_good_head = False  
  39.                         break  
  40.                 # if is a good head, then need to remove it from other lists  
  41.                 if is_good_head:  
  42.                     # save the head to the result list  
  43.                     has_good_head = True  
  44.                     res.append(head)  
  45.                     for al in merge_list:  
  46.                         if len(al) and al[0] == head:  
  47.                             del al[0]  
  48.                     break  
  49.                 # else skip to the next list  
  50.         if not has_good_head:  
  51.             raise TypeError("MRO error")  
  52.         if not processed:  
  53.             break  
  54.     return res  

reference:

  1. Python's Super Considered Harmful
  2. The Python 2.3 Method Resolution Order
  3. 《Core python programming》
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值