菱形问题、类的组合
菱形继承问题
在Java中和C#中子类只能继承一个父类,而Python中子类可以继承多个父类,这就会出现多个父类中有相同属性时的属性查找顺序问题。
菱形指的是类的继承最后会形成一个闭环,一个子类的多个父类最后都会再继承同一个父类,那么它们的继承关系就是菱形结构或钻石结构。
如果继承关系为菱形结构,那么属性查找的方式有两种:深度优先和广度优先。
深度优先
深度优先会顺着一个父类继承关系一层一层找,直到最后一层,没有找到则去第二个父类,当都没有找到属性时,就会抛出异常。
当类是经典类是,多继承情况下,在要查找属性不存在时,会按照深度优先的方式查找下去。
# 在Python2中执行.
class G:
def f1(self):
print('G.f1')
class F(G):
def f1(self):
print('F.f1')
class E(G):
def f1(self):
print('E.f1')
class D(G):
def f1(self):
print('D.f1')
class C(F):
def f1(self):
print("C.f1")
class B(E):
def f1(self):
print('B.f1')
class A(B,C,D):
pass
a = A()
a.f1()
# 依次注释f1方法即可验证
广度优先
不找多个类最后继承的同一个类,直接去找下一个父类,直到最后一个直接父类才会去找最后继承的同一个父类。
当类是新式类时,多继承情况下,在要查找属性不存在时,会按照广度优先的方式查找下去。
# 在Python3中执行
class G(object):
def f1(self):
print('G.f1')
class F(G):
def f1(self):
print('F.f1')
class E(G):
def f1(self):
print('E.f1')
class D(G):
def f1(self):
print('D.f1')
class C(F):
def f1(self):
print("C.f1")
class B(E):
def f1(self):
print('B.f1')
class A(B,C,D):
pass
a = A()
a.f1()
只有当继承为菱形结构时,才会有这个广度优先和深度优先的区别,如果非菱形结构,则是按每个父类逐层查找属性直到最后一层,然后再去下一个父类依次查找。
Python3中判断是否是菱形结构并不包括object类。
C3算法与mro()方法
对于定义的每一个类,Python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。
print(A.mro())
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
class A:
def test(self):
print('from A') # 先执行这条,打印出from A
super().test1() # super会按照MRO列表查找,会直接查找基类,A里面没有则去B,所以打印from B
class B:
def test1(self):
print('from B')
class C(A,B):
def test1(self):
print('from C')
obj = C()
obj.test()
print(C.mro())
结果为:
from A
from B
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 1、子类会先于父类被检查。
- 2、多个父类会根据它们在列表中的顺序被检查。
- 3、如果对下一个类存在两个合法的选择,会选择第一个父类。
类的组合
在一个类中以另外一个类的对象作为数据属性,称为类的组合。
class Equip:
def fire(self):
print('release Fire skill')
class River:
camp = 'Noxus'
def __init__(self,nickname):
self.nickname = nickname
self.equip = Equip() # 用Equip类产生一个装备,赋值给实例的equip属性。
r1 = RIven('瑞文')
r1.equip.fire() # 可以使用组合的类产生的对象所持有的方式。
release Fire skill
继承与组合都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同:
-
继承的方式
通过继承建立了派生类与基类之间的关系,它是一种 “ 是 ” 的关系,比如黑猩猩是猩猩,程序猿是人。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好。
-
组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种 “ 有 ” 的关系,比如,程序猿有生日,程序猿有开发技能。
当类之间有显著的不同,并且较小的类是较大的类所需要的组件时,用组合比较好。