最近在Python的学习中,在面向对象的部分遭遇了让我近期寝食难安的继承,主要是卡在了多继承(钻石继承)的问题上,以下是我关于这个问题的简单认识
1 认识Python的继承
1.1 继承的概念
继承是面向对象三大特性的重要组成部分,其作用是子类通过继承父类可以获得来自父类的除私有内容以外的其他内容,继承的使用得以让开发人员在开发阶段可以省去大量的重复性的工作,也使项目的迭代和维护更加便捷。
1.2 单继承与多继承
单继承:子类只允许继承一个父类,这样的继承方式优点在于其继承有序,逻辑结构清晰、语法简单且隐患少,而缺点则是子类的功能只能在当前唯一的继承中扩展。
多继承: 顾名思义,多继承允许子类继承多个父类,优点在于便于功能扩展,灵活性较高,其缺点也十分明显,来自多个父类的继承常常使得继承顺序非常混乱,在问题出现时令人难以处理,也正是由于这个原因,并不是所有编程语言都支持多继承。
2 先从单继承开始
以普通方法调用父类__init__函数
class A(object):
def __init__(self):
print("A init")
class B(A):
def __init__(self):
A.__init__(self)
print("B init")
class C(B):
def __init__(self):
B.__init__(self)
print("C init")
c = C()
运行结果为:
A init
B init
C init
使用super函数调用父类__init__函数
class A(object):
def __init__(self):
print("A init")
class B(A):
def __init__(self):
super(B, self).__init__()
print("B init")
class C(B):
def __init__(self):
super(C, self).__init__()
print("C init")
c = C()
运行结果为:
A init
B init
C init
3 简单的多继承——问题引入
3.1使用普通方法调用父类构造函数
class A(object):
def __init__(self):
print("A init")
class B(A):
def __init__(self):
A.__init__(self)
print("B init")
class C(A):
def __init__(self):
A.__init__(self)
print("C init")
class D(B, C):
def __init__(self):
B.__init__(self)
C.__init__(self)
print("D init")
d = D()
运行结果为:
A init
B init
A init
C init
D init
从运行结果可以看到A被初始化了两次
3.2 使用super调用父类构造函数
class A(object):
def __init__(self):
print("A init")
class B(A):
def __init__(self):
super(B, self).__init__()
print("B init")
class C(A):
def __init__(self):
super(C, self).__init__()
print("C init")
class D(B, C):
def __init__(self):
super(D, self).__init__()
print("D init")
d = D()
运行结果为:
A init
C init
B init
D init
可以看到使用super调用父类的构造函数A只初始化了一次
4 多继承经典问题——钻石继承
(请自动忽略画图超丑这一因素)
如上图所示的继承关系称为钻石继承,上文提到的多继承的弱点之继承关系混乱也在图中有所体现(觉得不够混乱的童鞋请自行脑补)
4.1 各语言解决钻石继承问题的方法
C++:使用虚拟继承解决钻石继承问题
Java:禁止使用多继承
Python:使用super方法
4.2 super方法与MRO
想要使用super方法,首先要了解super的内核——MRO(Method Resolution Order)函数解析顺序
MRO是Python通过C3线性算法(此处不再详述)根据每一个定义的类计算出的一个函数解析顺序列表,它代表了类继承的顺序,可以通过classname.__mro__获取此列表
MRO列表遵循的三个原则:
子类永远在父类前面
如果存在多个父类,会根据列表中的顺序依次查找
如果下一个类存在两个合法的选择,选择第一个父类
了解了MRO是什么,那么MRO究竟做了什么呢?又解决了哪些问题呢?
通过MRO,Python将多继承的图结构转为list的线性结构,使super在继承体系中向上查找的过程变为了在mro列表中向右查找的过程,并且任何类只会被处理一次。
通过这个方法,Python解决的多继承中的两大难题:
1 查找顺序问题,从Leaf的mro顺序可以看出,如果Leaf通过super来访问父类成员,那么Medium1的成员会在Medium2之前先被访问到。如果Medium1和Medium2都没有找到,最后再到Base中查找。
2 钻石继承的多次初始化问题。在mro的list中Base类只出现了一次,事实上任何类都只会在mro中出现一次,这就确保了super向上调用的过程中任何祖先类的方法都只会被执行一次
5 super方法的使用
5.1 help(super)
5.2 super(type, obj)
当我们在D的__init__中写这样的super时:
class D(B, C):
def __init__(self):
super(D, self).__init__()
print “D init”
super(D, self).init()的意思是说:
获取self所属类的mro, 也就是[D, B, C, A]
从mro中Leaf右边的一个类开始,依次寻找__init__函数。这里是从B开始寻找
一旦找到,就把找到的__init__函数绑定到self对象,并返回。
从这个执行流程可以看到,如果我们不想调用B的__init__,而想要调用C的__init__,那么super应该写成:super(B, self)init()
5.3 super(type, type2)
当我们在Leaf中写类方法的super时:
class D(B, C):
def __new__(cls):
obj = super(D, cls).__new__(cls)
print “Dnew”
return obj
super(D, cls).new(cls)的意思是说:
-获取cls这个类的mro,这里也是[D, B, C, A]
从mro中D右边的一个类开始,依次寻找__new__函数
一旦找到,就返回“ 非绑定 ”的__new__函数
由于返回的是非绑定的函数对象,因此调用时不能省略函数的第一个参数。这也是这里调用__new__时,需要传入参数cls的原因
同样的,如果我们想从某个mro的某个位置开始查找,只需要修改super的第一个参数就行
6 小结
1 Python调用父类成员共有2种方法:
普通方法
super方法
2 钻石继承中普通方法会遇到Base两次初始化的问题
3 了解了其他语言对这个问题的解决方法,并以实例展示了Python用super可以解决此问题
4 了解了super的内核:mro的知识和原理
5 讲解了super两种主要的用法及原理