之前看资料python2.x的继承是深度优先遍历,python3.x的继承是广度优先遍历,但是代码运行起来两者的方法解析顺序是一样的,很是疑惑,所以深入学习了一下,做个笔记。



首先,需要知道关于遍历的一些概念:

所谓遍历(Traversal),是指沿着某条搜索路线,依次对树(或图)中每个节点均做一次访问。

深度优先遍历从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续找它的下一个新的顶点进行访问,重复此步骤,直到所有结点都被访问完为止。

广度优先遍历从某个顶点出发,首先访问这个顶点,然后找出这个结点的所有未被访问的邻接点,访问完后再访问这些结点中第一个邻接点的所有结点,重复此方法,直到所有结点都被访问完为止。

可以看到深度优先与广度两种方法最大的区别在于前者从顶点的第一个邻接点一直访问下去再访问顶点的第二个邻接点;后者从顶点开始访问该顶点的所有邻接点再依次向下,一层一层的访问。



其次要明确一点,python2.x默认是经典类,但是它也可以创建新式类,但是python3.x中只支持新式类,而经典类的继承是深度优先遍历,新式类的继承是广度优先遍历。这也是为什么之前我的代码运行结果一样的原因,我创建的都是新式类。

经典类与新式类的区别:

经典类:没有继承自object,从左至右的深度优先遍历,可以用 inspect.getmro 来获取类的 MRO

新式类:继承自object,从左至右的广度优先遍历,可以直接通过__mro__属性获取类的 MRO


经典类与新式类子类继承父类__init__写法:

经典类写法:父类.__init(self, …)

新式类写法:

python2.x super(子类, self)._init_(…)

如果是python2.x,最上层的父类一定要继承object,否则会报错,因为python2.x默认经典类

python3.x super()._init_(…)


经典类与新式类子类继承父类方法的写法:

经典类写法:父类.方法名()

新式类写法:

python2.x super(子类, self).run()

python3.x super().run()


经典类案例
import inspect


class A:
    def run(self):
        print('---A---')

class B(A):
    def run(self):
        A.run(self)
        print('---B---')

class C(A):
    def run(self):
        A.run(self)
        print('---C---')

class D(B):
    def run(self):
        B.run(self)
        print('---D---')

class E(C):
    def run(self):
        C.run(self)
        print('---E---')

class F(E, D):
    def run(self):
		E.run(self)
		D.run(self)
		print('---F---')


f = F()
f.run()

print(inspect.getmro(F))

代码运行结果:

---A---
---C---
---E---
---A---
---B---
---D---
---F---
(<class __main__.F at 0x7fceacfe62c0>, <class __main__.E at 0x7fceacfe6258>, <class __main__.C at 0x7fceacfe6188>, <class __main__.A at 0x7fceacfe60b8>, <class __main__.D at 0x7fceacfe61f0>, <class __main__.B at 0x7fceacfe6120>)

按照深度遍历,其顺序为 [F, E, C, A, D, B, A],重复类只保留第一个,因此变为 [F, E, C, A, D, B]。


新式类案例
class A(object):
    def run(self):
        # super().run()
        print('---A---')

class B(A):
    def run(self):
        # super().run()
        super(B, self).run()
        print('---B---')

class C(A):
    def run(self):
        # super().run()
        super(C, self).run()
        print('---C---')

class D(B):
    def run(self):
        # super().run()
        super(D, self).run()
        print('---D---')

class E(C):
    def run(self):
        # super().run()
        super(E, self).run()
        print('---E---')

class F(E, D):
    def run(self):
        # super().run()
        super(F, self).run()
        print('---F---')


f = F()
f.run()

print(F.__mro__)

代码运行结果:

---A---
---B---
---D---
---C---
---E---
---F---
(<class '__main__.F'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)


在多继承的树中,如果中间某层【C】有向上一层【A】解析的迹象,则会按照父类列表【F】中的顺序【未遍历的D结点】执行,这样的解析方式在Python中被称为广度遍历。

注:【】为此案例的解释,具体问题具体分析。

实际上,python 2.2 的新式类(new-style class)还有另一种 MRO,它是为解决经典类 MRO 所存在的问题而设计的,其 MRO 计算方式和经典类 MRO 的计算方式非常相似:采用从左至右的深度优先遍历,但是如果遍历中出现重复的类,只保留最后一个预计算。

上述例子,按照深度遍历顺序为 [F, E, C, A, object, D, B, A, object],重复类只保留最后一个,因此变为 [F, E, C, D, B, A, object]。

但是python2.2不会阻止程序员写出具有二义性的继承关系,在以后版本中又进行了改进,就是我们现在所用的C3算法

这儿看不懂的可以看 ,讲得非常详细。