关于多重继承
我比较赞成继承的本质是逐步细化,即逐步的补充原来抽象层次所欠缺的,不断补充具体的应用场景,最终达到应用层次。对于继承的原因,一般分为两种:第一种是对接口(界面约定)的实现,另一种是重载(override)。就像java语言的设计,对于接口(interface)的继承叫做实现
(implements),对于普通类的继承叫做扩展
(extends)。并且规定接口不能实例化,而多重继承必须是一个抽象类和多个接口类。这也是使用多重继承的一般性原则。
python中的新类和旧类
python中的继承比较麻烦。由于一开始的设计问题,python中分为新类和旧类。新类指所有继承了object对象的类,而旧类指所有没有继承object的类。
|
|
python旧类的方法继承顺序
对于旧类,多重继承中方法采用深度优先
算法。按顺序从一个父类一直深度搜索,然后再搜索第二个父类,一直到找到方法为止。
- 当只有一层继承时:
|
|
可以看到c.method
方法实际为 A.method
方法。
将 Child(A, B)
改变继承顺序为 Child(B, A)
再观察。
|
|
这时继承c.method
方法实际为B.method
方法。
按照继承参数列表中的顺序由前向后查找, C(A, B) => C -> A -> B
- 有多层继承时
|
|
可以看到, c.method
方法实际为 A.method
, 查找顺序为 C -> A -> AA
。
按照继承参数列表中的顺序由前向后,深度优先查找。
python旧类中的属性继承顺序
如果存在初始化函数,旧类只能通过显示调用父类的初始化函数。
|
|
类属性查找没有像类方法那样,采用深度优先顺序。我们知道,在python中属性是存放在 __dict__
中,而初始化就是给这个字典设置值。所以,后初始化中设置的属性值会冲掉先初始化设置的值。
因此,子类的属性由最后一个初始化的父类决定。
|
|
可见,初始化顺序决定了属性的初值。
python旧类继承的问题
- 祖类的多次初始化
python旧类有个很严重的问题, 就是共同祖类
的多次初始化。如果继承的多个父类他们有共同祖类(父类们有相同的父类),会导致祖类的多次初始化。
|
|
python新类的方法继承
为了解决祖类的多次初始化,以及属性与初始化顺序相关的问题(还有其他问题)。python设计了新类,并且用super
隐式进行父类的初始化。
但是新类有一个问题就是,任何多重继承的子类都有一个共同的祖类object
,都是菱形继承。
如果再按照深度优先,那么一些方法最终调用的都是object
的方法,而不是其他父类中定义的方法。
所以新类的方法是按照 klass.__mro__
中储存的类的顺序查找的。
|
|
可见是按照Child.__mro__
的顺序查找,而且object
永远是最后一个父类。
python新类的属性继承
在新类中,通过调用super
函数进行隐式的父类初始化,保持与方法继承同样的顺序,并且避免祖类的多次初始化。
|
|
不仅避免了祖类A的多次初始化,又保持与方法的查找都是按照 Child.__mro__
进行的。
新类的问题
问题一
super
函数不能兼容旧类。因此当继承旧类时不能使用super
初始化。
|
|
有两个办法解决这个问题。
办法一: 利用多重继承object
。
|
|
办法二: 将is-a
修改为has-a
,并且利用__getattr__
动态获取属性和方法。将继承修改为拥有关系。
|
|
问题二
当继承多个拥有初始化函数__init__
时,无法同时初始化。(强烈不建议这种情况下的多重继承)
|
|
按照Child.__mro
,直接调用 A.__init__
初始化函数,而不会调用B.__init__
初始化函数。
改变继承顺序Child(A, B)
->Child(B, A)
观察其变化。
|
|
此时初始化需要3个参数,可见调用的是 B.__init__
。
有一个办法解决这个问题。
像旧类那样,显式的分别初始化父类
|
|
python多重继承的一般性原则
1. 永远使用新类, 继承object; 不继承旧类。
2. 多重继承只在父类中只有一个有初始化函数的情况下。
简单来说,就是像java那样,只采用一个类为基类,其他为接口的情况下