在面向对象的编程语言中,有一个稍微复杂且较少被使用的特性,那就是多继承。在计算机专业的考试中,可能会较多出现多继承的考题,因为它够复杂。实际应用中,除了一些特殊的场景,很少会用到多继承。
但是,在笔者看来,学习Python,还是有必要了解一下Python中的多继承的概念,从而更好地理解相关特性背后的设计理念,在阅读相关源码中,遇到多继承不至于手足无措。
今天的文章,接着上一篇文章,继续聊一下Python中面向对象的第二个特性,也就是继承中的多继承的部分。
编程语言关于多继承的设计实现多继承的概念并不是Python所独有的,其他面向对象的编程语言中,也同样有多继承的设计实现,只是具体的设计、实现方式会有所不同。以C++、Java和Python为例,简单看一下关于多继承的不同设计理念。
首先,C++中是支持多继承的,也就是一个类可以继承自多个父类。但是,不可避免的会遇到所谓的“菱形继承问题(Diamond Problem)”,即一个类继承自两个类,而这两个类又同时继承自同一个类。这样可能会导致基类的属性和方法被多次调用或者初始化。C++中通过所谓“虚继承(Virtual Inheritance)”来解决这个问题。
其次,Java是明确不支持多继承的语法的,主要是多继承的引入确实会导致程序变得更加复杂,且很多时候滥用多继承会导致逻辑混乱、难以理解。但是,Java中通过区分继承和实现来间接支撑了近似多继承的概念。Java中一个类只能继承自一个类,而不是多个。但是,Java中提供了使用接口以定义功能的特性。一个类虽然只能继承自一个类,但是,可以同时实现多个接口,以扩展类的功能,从结果来看,间接实现了多继承的效果,而且设计上更加符合逻辑。
然后,就是我们现阶段的主角Python了,Python中支持多继承,虽然会使程序的复杂性提升,但是通过MRO(方法解析,前文已经稍微提及)和super内置类,确保多继承的方法解析变得更加明确、一致、可预测,从而简化对多继承代码的理解。
简单介绍了不同语言关于多继承的设计,接下来,回到今天的主题,Python中多继承的介绍。
Python的多继承首先通过代码实例,来简单看一下Python中多继承的语法,假设有这样一个简化的设计:
1、我们已有的打工人类,体现社会、职场中的人的属性、方法;
2、我们又定义了一个自然人的类,更多的体会人在自然中的属性和方法;
3、程序员应该同时具有打工人和自然人的特性,也就是程序员同时具备打工人和自然人的双重属性,是一种既是又是的关系。
代码实现:
执行结果:
从代码中,可以看到多继承的语法是相对简单的,通过多继承,子类可以同时获得多个父类的属性和方法,同时实现更大范围的代码复用。
但是,这个示例,只是简化了多继承的使用,逻辑也是相对简单的。接下来,我们稍微拧巴一下,探讨一下稍微复杂一点的情况:同名方法的覆盖与解析。
假如现在对打工人和自然人都添加一个吃饭的同名方法,代码稍微调整简化一下,这时候代码的执行会是什么结果呢?
执行结果:
如果我们调整一下,继承中两个父类的顺序,重新执行一下:
可以看到,两个父类中的同名方法,跟定义多继承时的顺序有关。其实,真实的顺序或许更加复杂。
DFS、BFS或者其他Python中支持多继承,且保证固定、一致、可预测的解析顺序,那么这是什么样一种顺序呢。稍微了解过《数据结构》的内容的同鞋,应该听说过两种树结果的遍历算法:DFS(Depth-First Search,深度优先)和BFS(Breadth-First Search,广度优先)。
Python中多继承中,方法的解析顺序会不会是DFS或者BFS中的一种呢。
我们简化一下代码,看下这样一个继承关系(不考虑object)
A、C、D分别定义了同名方法,代码如下:
执行结果:
从执行结果来看,实际调用了从A中继承的方法,所以,这种情况下,A、C、D都有method方法,但是实际调用了A的方法,而没有选择D的方法,看似采用了的DFS。
但是,如果我们换成这样一种继承关系呢:
这种继承关系,很像是一个菱形,就是大名鼎鼎的菱形继承/钻石继承。
A和C定义了同名方法,具体代码如下:
执行结果:
从执行结果来看,实际调用了从C中继承的方法,所以,这种情况下,A、C都有method方法,但是实际调用了C的方法,而没有选择A的方法,看似又采用了的BFS。
其实,前面已经提到过,我们通过类的__mro__属性或者mro()方法,可以获得到方法的解析顺序,这个顺序在同一个继承关系中是固定、一致的。
而这个顺序既不是通过单纯的DFS生成,也不是通过BFS生成,而是基于C3线性化算法来实现的,这个算法保证如下三个原则:
1、一致性原则:一个类的MRO应该包含直接父类的MRO,并且保持他们的顺序。
2、本地优先原则:在MRO中,子类出现在父类的前面。
3、单调性原则:如果一个类的MRO包含另一个类的MRO,则该顺序在整个继承链中保持不变。
当然,这三个原则简单了解一下即可,感兴趣的可以细品……
总结本文简单介绍了不同语言中多继承的设计实现方式,并通过代码介绍了Python中多继承中关于MRO顺序的设计实现,从而,在后续阅读到有关多继承的代码中,能够理解其中的解析实现方式及执行结果。
虽然Python支持多继承,但是使用多继承会导致代码的复杂性的增加,并且降低代码的可读性,所以,在实际使用中,非必要,应该尽量减少多继承的使用。
感谢您的拨冗阅读。