• Python多重继承


    前面介绍的大部分的继承都是单继承,既一个子类只有一个父类,但是Python也支持多重继承,即一个子类可以有多个父类。多继承有复杂的父类冲突问题,大部分的面向对象语言都仅仅支持单继承,Python是为数不多支持多继承的语言,本文对此展开学习。

    多继承的语法结构

    多继承的语法结构一般如下:

    1. class SubClassName(BaseClass1,BaseClass2,…):
    2. def __init__(self, *args):

    当然,子类所继承的所有父类同样也能有自己的父类,这样就可以得到一个继承关系机构图如下图所示:

    父类也许很复杂,在多继承中比较难处理的是菱形的继承结构:

    我们用实例来分析多继承的情况:

    1. import os
    2. class Base():
    3. def __init__(self):
    4. print(f'Base.__init__')
    5. def foo(self):
    6. print(f'Base.foo')
    7. class Base1(Base):
    8. def __init__(self):
    9. print(f'Base1.__init__')
    10. super().__init__()
    11. def foo(self):
    12. print(f'Base1.foo')
    13. class Base2(Base):
    14. def __init__(self):
    15. print(f'Base2.__init__')
    16. super().__init__()
    17. def foo(self):
    18. print(f'Base2.foo')
    19. pass
    20. class Grand(Base1, Base2):
    21. pass
    22. g = Grand()
    23. g.foo()
    24. ‘’'
    25. Base1.__init__
    26. Base2.__init__
    27. Base.__init__
    28. Base1.foo
    29. ‘''

     从上面的例子可以看到:

    • 孙对象的__init__方法调用了两个父对象的__init__方法,但是只调用了一次祖父对象的__init__方法
    • 孙对象的foo方法只调用了第一个父对象的foo方法,未调用第二个父对象和祖父对象的foo方法。

    甚至可以跨代混合继承:

    1. import os
    2. class Base():
    3. def __init__(self):
    4. print(f'Base.__init__')
    5. def foo(self):
    6. print(f'Base.foo')
    7. class Base1(Base):
    8. def __init__(self):
    9. print(f'Base1.__init__')
    10. super().__init__()
    11. def foo(self):
    12. print(f'Base1.foo')
    13. class Base2(Base):
    14. def __init__(self):
    15. print(f'Base2.__init__')
    16. super().__init__()
    17. def foo(self):
    18. print(f'Base2.foo')
    19. pass
    20. class Grand(Base1, Base):
    21. pass
    22. g = Grand()
    23. g.foo()
    24. ‘’’
    25. Base1.__init__
    26. Base.__init__
    27. Base1.foo
    28. ’‘’

    但是也有禁区,解释器无法区分应该使用哪个父类方法的场景:

    1. import os
    2. class Base():
    3. def __init__(self):
    4. print(f'Base.__init__')
    5. def foo(self):
    6. print(f'Base.foo')
    7. class Base1(Base):
    8. def __init__(self):
    9. print(f'Base1.__init__')
    10. super().__init__()
    11. def foo(self):
    12. print(f'Base1.foo')
    13. class Base2(Base):
    14. def __init__(self):
    15. print(f'Base2.__init__')
    16. super().__init__()
    17. def foo(self):
    18. print(f'Base2.foo')
    19. pass
    20. class Grand(Base, Base2):
    21. pass
    22. g = Grand()
    23. g.foo()
    24. ‘’’
    25. TypeError: Cannot create a consistent method resolution
    26. order (MRO) for bases Base, Base2
    27. ’‘’

    MRO

    MRO(Method Resolution Order)也叫方法解析顺序。可以通过type().mro()查询类的解析顺序。

    1. import os
    2. class Base():
    3. def __init__(self):
    4. print(f'Base.__init__')
    5. def foo(self):
    6. print(f'Base.foo')
    7. class Base1(Base):
    8. def __init__(self):
    9. print(f'Base1.__init__')
    10. super().__init__()
    11. def foo(self):
    12. print(f'Base1.foo')
    13. class Base2(Base):
    14. def __init__(self):
    15. print(f'Base2.__init__')
    16. super().__init__()
    17. def foo(self):
    18. print(f'Base2.foo')
    19. class Base3(Base):
    20. def __init__(self):
    21. print(f'Base3.__init__')
    22. super().__init__()
    23. def foo(self):
    24. print(f'Base3.foo')
    25. class Base31(Base1, Base2):
    26. pass
    27. class Base32(Base2, Base3):
    28. pass
    29. class Grand(Base32, Base31):
    30. pass
    31. g = Grand()
    32. g.foo()
    33. print(Grand.mro())
    34. ‘’’
    35. Base1.__init__
    36. Base2.__init__
    37. Base3.__init__
    38. Base.__init__
    39. Base1.foo
    40. [<class '__main__.Grand'>, <class '__main__.Base32'>, <class '__main__.Base31'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.Base3'>, <class '__main__.Base'>, <class 'object'>]
    41. ’‘’

    对于只支持单继承的编程语言来说,MRO 很简单,就是从当前类开始,逐个搜索它的父类。对于多继承,MRO 相对会复杂一些。Python 发展至今,经历了以下 3 种 MRO 算法,分别是:

    1. 从左往右,采用深度优先搜索(DFS)的算法,称为旧式类的 MRO;
    2. 自 Python 2.2 版本开始,新式类在采用深度优先搜索算法的基础上,对其做了优化;
    3. 自 Python 2.3 版本,对新式类采用了 C3 算法。由于 Python 3.x 仅支持新式类,所以该版本只使用 C3 算法。

    旧式类MRO算法

    在使用旧式类的 MRO 算法时,以下面代码为例:

    1. class A:
    2. def method(self):
    3. print("CommonA")
    4. class B(A):
    5. pass
    6. class C(A):
    7. def method(self):
    8. print("CommonC")
    9. class D(B, C):
    10. pass
    11. D().method()

    通过分析可以想到,此程序中的 4 个类是一个“菱形”继承的关系,当使用 D 类对象访问 method() 方法时,根据深度优先算法,搜索顺序为D->B->A->C->A

    使用旧式类的 MRO 算法最先搜索得到的是基类 A 中的 method() 方法,即在 Python 2.x 版本中,此程序的运行结果为:

    CommonA

    但是,这个结果显然不是想要的,我们希望搜索到的是 C 类中的 method() 方法。

    新式类MRO算法

    为解决旧式类 MRO 算法存在的问题,Python 2.2 版本推出了新的计算新式类 MRO 的方法,它仍然采用从左至右的深度优先遍历,但是如果遍历中出现重复的类,只保留最后一个。

    仍以上面程序为例,通过深度优先遍历,其搜索顺序为D->B->A->C->A,由于此顺序中有 2 个 A,因此仅保留后一个,简化后得到最终的搜索顺序为D->B->C->A

    新式类可以直接通过 类名.__mro__ 的方式获取类的 MRO,也可以通过 类名.mro() 的形式,旧式类是没有 __mro__ 属性和mro() 方法的。

    可以看到,这种 MRO 方式已经能够解决“菱形”继承的问题,但是可能会违反单调性原则。所谓单调性原则,是指在类存在多继承时,子类不能改变基类的 MRO 搜索顺序,否则会导致程序发生异常。

    例如:

    1. class X(object):
    2. pass
    3. class Y(object):
    4. pass
    5. class A(X,Y):
    6. pass
    7. class B(Y,X):
    8. pass
    9. class C(A, B):
    10. pass

    通过进行深度遍历,得到搜索顺序为C->A->X->object->Y->object->B->Y->object->X->object,再进行简化(相同取后者),得到C->A->B->Y->X->object

    下面来分析这样的搜索顺序是否合理,我们来看下各个类中的 MRO:

    • 对于 A,其搜索顺序为 A->X->Y->object;
    • 对于 B,其搜索顺序为 B->Y->X->object;
    • 对于 C,其搜索顺序为 C->A->B->X->Y->object。

    可以看到,B 和 C 中,X、Y 的搜索顺序是相反的,也就是说,当 B 被继承时,它本身的搜索顺序发生了改变,这违反了单调性原则。

    MRO C3

    为解决 Python 2.2 中 MRO 所存在的问题,Python 2.3 采用了 C3 方法来确定方法解析顺序。多数情况下,如果某人提到 Python 中的 MRO,指的都是 C3 算法。C3算法解决了单调性问题和只能继承无法重写问题,具体的MRO C3算法比较复杂(The Python 2.3 Method Resolution Order | Python.org),就不在这里叙述了。

    super()

    Python 的内置函数 super() 用于调用父类(超类)的一个方法,用来解决多重继承问题的。不仅仅可以调用父类的构造函数,还可以调用父类的成员函数。

    super() 内置函数(对象)返回一个代理对象(超类的临时对象),允许我们访问基类的方法。

    1. class Person(object):
    2. def eat(self, times):
    3. print(f'我每天吃{times}餐。')
    4. class Student(Person):
    5. def eat(self):
    6. # 调用超类
    7. super().eat(4)
    8. tom = Student()
    9. tom.eat()
    10. # 我每天吃4餐。
    11. Student.mro()
    12. # [__main__.Student, __main__.Person, object]

    调用父类初始化继承:

    1. class Base(object):
    2. def __init__(self, a, b):
    3. self.a = a
    4. self.b = b
    5. class A(Base):
    6. def __init__(self, a, b, c):
    7. super().__init__(a, b)
    8. #super(A, self).__init__(a, b) # Python2 写法
    9. self.c = c
    10. a = A(1,2,3)
    11. A.mro()
    12. # [__main__.A, __main__.Base, object]

    语法

    它其实是一个内置的类,语法如下:

    1. class super(self, /, *args, **kwargs)
    2. # or
    3. super(type[, object-or-type])

    返回一个代理对象,它会将方法调用委托给 type 的父类或兄弟类。 这对于访问已在类中被重载的继承方法很有用。但特别注意,调用的是type指定的父类或兄弟类,具体调用哪一个类,取决于MRO的顺序,也就是说,实际上是调用的MRO列表上,type指定的类的下一个类,看下面的例子:

    1. import os
    2. class Base():
    3. def __init__(self):
    4. print(f'Base.__init__')
    5. def foo(self):
    6. print(f'Base.foo')
    7. class Base1(Base):
    8. def __init__(self):
    9. print(f'Base1.__init__')
    10. super().__init__()
    11. def foo(self):
    12. print(f'Base1.foo')
    13. class Base2(Base):
    14. def __init__(self):
    15. print(f'Base2.__init__')
    16. super().__init__()
    17. def foo(self):
    18. print(f'Base2.foo')
    19. pass
    20. class Grand(Base1, Base2):
    21. def __init__(self):
    22. print(f'Grand.__init__')
    23. super().__init__()
    24. def foo(self):
    25. print(f'Grand.foo')
    26. super(Base1, self).foo()
    27. g = Grand()
    28. g.foo()
    29. print(Grand.mro())
    30. ‘’'
    31. Grand.__init__
    32. Base1.__init__
    33. Base2.__init__
    34. Base.__init__
    35. Grand.foo
    36. Base2.foo
    37. [__main__.Grand'>, __main__.Base1'>, __main__.Base2'>, __main__.Base'>, object'>]
    38. ‘''

    foo()函数打印的结果让你震惊,因为你写的是

    super(Base1, self).foo()

    但是实际输出的是

    Base2.foo

    而Base2与Base1其实根本就没有直接的关系,怎么会调用到Base2的foo了呢?

    是因为super()根本就不看Base1的继承关系,而是看Grand的MRO,可以看到,Grand的MRO的顺序上,Base2是Base1的下一个类,而super(Base1,self).foo()调用的正是Base1下一个类的foo()方法。 

    注意:如果省略第二个参数,则返回的超类对象是未绑定的。 如果第二个参数为一个对象,则 isinstance(obj, type) 必须为真值。 如果第二个参数为一个类型,则 issubclass(type2, type) 必须为真值(这适用于类方法)。

    除了方法查找之外,super() 也可用于属性查找。 一个可能的应用场合是在上级或同级类中调用 描述器(任何定义了 __get__()__set__() 或 __delete__() 方法的对象)。
    请注意 super() 是作为显式加点属性查找的绑定过程的一部分来实现的,例如 super().__getitem__(name)。 它做到这一点是通过实现自己的 __getattribute__() 方法,这样就能以可预测的顺序搜索类,并且支持协作多重继承。 对应地,super() 在像 super()[name] 这样使用语句或操作符进行隐式查找时则未被定义。
    还要注意的是,除了零个参数的形式以外,super() 并不限于在方法内部使用。 两个参数的形式明确指定参数并进行相应的引用。 零个参数的形式仅适用于类定义内部,因为编译器需要填入必要的细节以正确地检索到被定义的类,还需要让普通方法访问当前实例。

    1. import os
    2. class Base():
    3. def __init__(self):
    4. print(f'Base.__init__')
    5. def foo(self):
    6. print(f'Base.foo')
    7. class Base1(Base):
    8. def __init__(self):
    9. print(f'Base1.__init__')
    10. super().__init__()
    11. def foo(self):
    12. print(f'Base1.foo')
    13. class Base2(Base):
    14. def __init__(self):
    15. print(f'Base2.__init__')
    16. super().__init__()
    17. self.__nicename__ = 'Base2'
    18. def foo(self):
    19. print(f'Base2.foo')
    20. pass
    21. class Grand(Base1, Base2):
    22. def __init__(self):
    23. print(f'Grand.__init__')
    24. super().__init__()
    25. def foo(self):
    26. print(f'Grand.foo')
    27. super(Base1, self).foo()
    28. print('__getattribute__:', super().__getattribute__('__nicename__'))
    29. g = Grand()
    30. g.foo()
    31. print(Grand.mro())
    32. ‘’'
    33. Grand.__init__
    34. Base1.__init__
    35. Base2.__init__
    36. Base.__init__
    37. Grand.foo
    38. Base2.foo
    39. __getattribute__: Base2
    40. [__main__.Grand'>, __main__.Base1'>, __main__.Base2'>, __main__.Base'>, object'>]
    41. ‘''

  • 相关阅读:
    【C++入门】C语言的不足之处
    java-php-net-python-在线音乐播放网站.计算机毕业设计程序
    【数字信号处理】离散傅里叶变换(DFT)的物理意义
    SpringBoot学习笔记(项目创建,yaml,多环境开发,整合mybatis SMM)
    MS5611大气压强传感器驱动代码(基于GD32F103)
    论文精读:SimGNN: A Neural Network Approachto Fast Graph Similarity Computation
    CSS 笔记
    数码管时钟--LABVIEW编程
    跨域请求的方法
    IB心理学生物分析模块
  • 原文地址:https://blog.csdn.net/spiritx/article/details/133234598