首先得说明的是,Python的类分为经典类 和 新式类
经典类是python2.2之前的东西,但是在2.7还在兼容,但是在3之后的版本就只承认新式类了
新式类在python2.2之后的版本中都可以使用
object
这个基类的:# old style
class A():pass
# new style
class A(obejct):pass
2.经典类在类多重继承的时候是采用从左到右深度优先
原则匹配方法的..而新式类是采用C3算法
(不同于广度优先)进行匹配的
3.经典类是没有__MRO__
和instance.mro()
调用的,而新式类是有的.
因为在经典类中的多重继承会有些问题…可能导致在继承树中的方法查询绕过后面的父类:
class A():
def foo1(self):
print \"A\"
class B(A):
def foo2(self):
pass
class C(A):
def foo1(self):
print \"C\"
class D(B, C):
pass
d = D()
d.foo1()
按照经典类的查找顺序从左到右深度优先
的规则,在访问d.foo1()
的时候,D这个类是没有的..那么往上查找,先找到B,里面没有,深度优先,访问A,找到了foo1(),所以这时候调用的是A的foo1(),从而导致C重写的foo1()被绕过.
所以python引入了新式类的概念,每个基类都继承自object
并且,他的匹配规则也从深度优先
换到了C3
C3算法是怎么做匹配的呢..在问答版块上面讨论之后,归结如下:
C3算法的一个核心是merge
.
在merge列表中,如果第一个序列mro的第一个类是出现在其它序列,并且也是第一个,或者不出现其它序列,那么这个类就会从这些序列中删除,并合到访问顺序列表中
比如:(引用问题中zhuangzebo的回答@zhuangzebo)
class A(O):pass
class B(O):pass
class C(O):pass
class D(A,B):pass
class E(C,D):pass
首先需要知道 O(object)的mro
(method resolution order)列表是[O,]
那么接下来是:
mro(A) = [A, O]
mro(B) = [B, O]
mro(C) = [C, O]
mro(D) = [D] + merge(mro(A), mro(B), [A, B])
= [D] + merge([A, O], [B, O], [A, B])
= [D, A] + merge([O], [B, O], [B])
= [D, A, B] + merge([O], [O])
= [D, A, B, O]
mro(E) = [E] + merge(mro(C), mro(D), [C, D])
= [E] + merge([C, O], [D, A, B, O], [C, D])
= [E, C] + merge([O], [D, A, B, O], [D])
= [E, C, D] + merge([O], [A, B, O])
= [E, C, D, A, B] + merge([O], [O])
= [E, C, D, A, B, O]
然后还有一种特殊情况:
比如:merge(DO,CO,C)
先merge的是Dmerge(DO,CO,C)
先merge的是C
意思就是.当出现有 一个类出现在两个序列的头(比如C)
这种情况和 这个类只有在一个序列的头(比如D)
这种情况同时出现的时候,按照顺序方式匹配。
新式类生成的访问序列被存储在一个叫MRO的只读列表中..
你可以使用instance.__MRO__
或者instance.mro()
来访问
最后匹配的时候就按照MRO序列的顺序去匹配了
举个例子就完全明白了:
class A(object):pass
class B(A):pass
class C(B):pass
class D(A):pass
class E(D):pass
class F(C, E):pass
按照广度优先遍历,F的MRO序列应该是[F,C,E,B,D,A]
但是C3是[F,E,D,C,B,A]
意思是你可以当做C3是在一条链路上深度遍历到和另外一条链路的交叉点,然后去深度遍历另外一条链路,最后遍历交叉点
super
和按类名访问
问题在经典类中,你如果要访问父类的话,是用类名来访问的..
class A():
def __init__(self):
print \"A\"
class B(A):
def __init__(self):
print \"B\"
A.__init__(self) #python不会默认调用父类的初始化函数的
这样子看起来没三问题,但是如果类的继承结构比较复杂,会导致代码的可维护性很差
..
所以新式类推出了super
这个东西…
class A():
def __init__(self):
print \"A\"
class B(A):
def __init__(self):
print \"B\"
super(B,self).__init__()
这时候,又有一个问题:当类是多重继承的时候,super访问的是哪一个类呢?
super实际上是通过__MRO__
序列来确定访问哪一个类的…实际上就是调用__MRO__
中此类后面的一个类的方法.
比如序列为[F,E,D,C,B,A]
那么F中的super就是E,E的就是D
super
和按照类名访问
混合使用带来的坑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\"
这时候打印出来是:
enter F
enter E
enter B
leave B
enter C
enter D
enter A
leave A
leave D
leave C
leave E
enter D
enter A
leave A
leave D
leave F
可以看出来D和A的初始化函数被乱入了两次!按类名访问
就相当于C语言之前的GOTO
语句…乱跳,然后再用super
按顺序访问..就有问题了
所以建议就是要么一直用super
,要么一直用按照类名访问