Python 的类分为经典类与新式类。Python2.7之前的版本中可以采用经典类,经典类继承父类的顺序采用深度优先算法,但在Python3之后的版本就只承认新式类了。新式类在python2.2之后的版本中都可以使用,新式类的继承顺序采用C3算法,其继承顺序可以通过查看MRO列表获取。

经典类与新式类的区别

  1. 经典类是默认没有派生自某个基类的,而新式类默认派生自object基类:

    1
    2
    3
    4
    5
    6
    7
    # old style
    class A():
    pass

    # new style
    class A(object):
    pass
  2. 经典类在类多重继承的时候是采用的从左至右深度优先的匹配方法,而新式类采用C3算法(不同于广度优先)进行匹配的。

  3. 经典类没有__MRO__instance.mro()调用,而新式类有。

经典类中的继承问题

经典类中采用深度优先的匹配方法,可能导致在查询继承树中绕过后面的父类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class D():
def foo(self):
print "class D"

class B(D):
pass

class C(D):
def foo(self):
print "class C"

class A(B, C):
pass

f = A()
f.foo()

其输出为:class D。 新式类采用C3算法(区别于广度优先的原则)进行搜索,若使用新式类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class D(object):
def foo(self):
print "class D"

class B(D):
pass

class C(D):
def foo(self):
print "class C"

class A(B, C):
pass

f = A()
f.foo()

输出为:class C 搜索的顺序如下图所示:

Python中类的方法解析顺序

C3算法

C3算法最早被提出是用于Lisp的,应用在Python中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。

  • 本地优先级:指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。
  • 单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。

深度优先搜索用栈(stack)来实现,整个过程可以想象成一个倒立的树形:

  1. 把根节点压入栈中。
  2. 每次从栈中弹出一个元素,搜索所有在它下一级的元素,把这些元素压入栈中。并把这个元素记为它下一级元素的前驱。
  3. 找到所要找的元素时结束程序。
  4. 如果遍历整个树还没有找到,结束程序。

广度优先搜索使用队列(queue)来实现,整个过程也可以看做一个倒立的树形:

  1. 把根节点放到队列的末尾。
  2. 每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱。
  3. 找到所要找的元素时结束程序。
  4. 如果遍历整个树还没有找到,结束程序。

对于同一段程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A(object):
def foo(self):
print "A"
class B(A):
def foo(self):
print "B"
class C(B):
def foo(self):
print "C"
class D(A):
def foo(self):
print "D"
class E(D):
def foo(self):
print "E"
class F(C, E):
def foo(self):
print "F"

f = F()
f.foo1()

当使用深度优先搜索,广度优先搜索及C3算法的不同搜索顺序如下:

DFS深度优先搜索(FCBAED)DFS深度优先搜索(FCBAED)
BFS广度优先搜索(FCEBDA)BFS广度优先搜索(FCEBDA)
C3算法(FCBEDA)C3算法(FCBEDA)

对于新式类,可以用instance.__mro__instance.mro()来查看其MRO(Method Resolution Order 方法解析顺序)列表。对于上文代码中的类F的MRO如下:

1
2
print F.mro()
(<class '__main__.F'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.D'>, <class '__main__.A'>, <type 'object'>)

即C3算法的解析结果。 同时为了解决多重继承中的调用父类问题,python2.2之后引入了super


参考资料:

  1. Method Resolution Order
  2. Python类的多重继承问题深入分析
  3. python类学习以及mro–多继承属性查找机制
  4. Python 类继承,__bases__, __mro__, super