MRO
Method resolution order是python用来解析方法调用顺序的。MRO对于多重继承中方法调用异常重要。python中有一个内建函数和MRO密切相关——super。顾名思义,super看上去应该是调用父类的方法,通常情况下也是如此。来看一段代码:
class A(object): def __init__(self): print 'A.__init__' class B(A): def __init__(self): print 'B.__init__' # try to call parent's __init__ without explicitly reference class A super(B, self).__init__() >>> x = B() B.__init__ A.__init__
这里我们通过super来调用父类的__init__,super(B, self)返回一个bounded对象(因为我们传入了self)。
从输出可以看到,调用正确。就像我们直接调用A.__init__(self)一样。
这样做的好处是,可以不用直接引用基类的名称就可以调用基类的方法。如果我们改变了基类的名称,那么所有子类的调用将不用改变。
但是super其实并不是我们想的那么简单,super不是简单地调用所谓基类的方法,而是调用MRO中的下一个类的方法,也就是类似于next的方法。
# 这段代码摘自Python's Super Considered Harmful class A(object): def __init__(self): print "A" super(A, self).__init__() class B(object): def __init__(self): print "B" super(B, self).__init__() class C(A): def __init__(self, arg): print "C","arg=",arg super(C, self).__init__() class D(B): def __init__(self, arg): print "D", "arg=",arg super(D, self).__init__() class E(C,D): def __init__(self, arg): print "E", "arg=",arg super(E, self).__init__(arg) #print "MRO:", [x.__name__ for x in E.__mro__] E(10)
对于这段代码,我们可能期望输出像这样:
E arg= 10 C arg= 10 A D arg= 10 B
但事实上,这段代码会引发错误,因为python没有像我们想的那样调用正确的函数。
E arg= 10 C arg= 10 A Traceback (most recent call last): File "C:/Users/Administrator/Desktop/example1-2.py", line 27, in <module> E(10) File "C:/Users/Administrator/Desktop/example1-2.py", line 24, in __init__ super(E, self).__init__(arg) File "C:/Users/Administrator/Desktop/example1-2.py", line 14, in __init__ super(C, self).__init__() File "C:/Users/Administrator/Desktop/example1-2.py", line 4, in __init__ super(A, self).__init__() TypeError: __init__() takes exactly 2 arguments (1 given)
我们先给出上面的代码中注释掉的输出mro的语句的输出:
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
MRO Implementation
这个mro是根据The Python 2.3 Method Resolution Order中的描述,自己写出来的。该paper中也有相关的实现,而且更加精巧。
import inspect def compute_linearization(kls): """ Given a class object, calculate the mro of the class A linerization is defined as the class plus the merge of the linerization of all bases and the list of bases """ if inspect.isclass(kls): mro = [kls] # for each base class, we need to compute the linerization merge_list = [] for basekls in kls.__bases__: merge_list.append(compute_linearization(basekls)) # add all bases to the merge list merge_list.append([]) for basekls in kls.__bases__: merge_list[-1].append(basekls) return mro + merge(merge_list) else: raise TypeError("argument must a class object") """ 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. """ def merge(merge_list): res = [] while True: processed = False has_good_head = False for i, l in enumerate(merge_list): if len(l): # mark for processing processed = True head = l[0] is_good_head = True other_lists = merge_list[0:i] + merge_list[i+1:] # check if the head is in the tail of other lists for rest in other_lists: if head in rest[1:]: is_good_head = False break # if is a good head, then need to remove it from other lists if is_good_head: # save the head to the result list has_good_head = True res.append(head) for al in merge_list: if len(al) and al[0] == head: del al[0] break # else skip to the next list if not has_good_head: raise TypeError("MRO error") if not processed: break return res
reference: