菱形问题、类的组合


菱形继承问题

在Java中和C#中子类只能继承一个父类,而Python中子类可以继承多个父类,这就会出现多个父类中有相同属性时的属性查找顺序问题。

菱形指的是类的继承最后会形成一个闭环,一个子类的多个父类最后都会再继承同一个父类,那么它们的继承关系就是菱形结构或钻石结构。

如果继承关系为菱形结构,那么属性查找的方式有两种:深度优先和广度优先。


深度优先

深度优先会顺着一个父类继承关系一层一层找,直到最后一层,没有找到则去第二个父类,当都没有找到属性时,就会抛出异常。

当类是经典类是,多继承情况下,在要查找属性不存在时,会按照深度优先的方式查找下去。

img

# 在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方法即可验证

广度优先

不找多个类最后继承的同一个类,直接去找下一个父类,直到最后一个直接父类才会去找最后继承的同一个父类。

当类是新式类时,多继承情况下,在要查找属性不存在时,会按照广度优先的方式查找下去。

92-菱形继承问题-新式类.png

# 在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

继承与组合都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同:

  1. 继承的方式

    通过继承建立了派生类与基类之间的关系,它是一种 “ 是 ” 的关系,比如黑猩猩是猩猩,程序猿是人。

    当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好。

  2. 组合的方式

    用组合的方式建立了类与组合的类之间的关系,它是一种 “ 有 ” 的关系,比如,程序猿有生日,程序猿有开发技能。

    当类之间有显著的不同,并且较小的类是较大的类所需要的组件时,用组合比较好。


原文地址:https://www.cnblogs.com/ChiRou/p/14206615.html