一文讲解python中的继承冲突及继承顺序

 更新时间:2024年03月31日 10:54:58   作者:宇航员写代码  
python支持多继承,如果子类没有重写方法,则默认会调用父类的方法,本文主要介绍了一文讲解python中的继承冲突及继承顺序,具有一定的参考价值,感兴趣的可以了解一下

简单的菱形继承

设计类如下

设计代码:

class Animal(object):
    def __init__(self, age: int = None, gender: int = None) -> None:
        print("Call the constructor of Animal.")
        self.m_age = age
        self.m_gender = gender
        self.m_name = "Animal"
        print("Ends the call to Animal's constructer.")
        pass

    def eat(self):
        print("Animal is eating")

    def sleep(self):
        print("Animal is sleeping")


class Tiger(Animal):
    def __init__(self, age: int = None, gender: int = None) -> None:
        print("Call the constructor of Tiger.")
        self.m_name = "Tiger"
        super().__init__(age, gender)
        print("Ends the call to Tiger's constructer.")

    def eat(self):
        print("Tiger is eating")

    pass


class Lion(Animal):
    def __init__(self, age: int = None, gender: int = None) -> None:
        print("Call the constructor of Lion.")
        self.m_name = "Lion"
        super().__init__(age, gender)
        print("Ends the call to Lion's constructer.")

    def eat(self):
        print("Lion is eating")

    def sleep(self):
        print("Lion is sleeping")
    pass


class Liger(Tiger, Lion):
    def __init__(self, age: int = None, gender: int = None) -> None:
        super().__init__(age, gender)
    pass


if __name__ == '__main__':
    liger = Liger(8, 1) #实例化一个`Liger`
    print(Liger.__mro__)
    print(liger.m_name)
    liger.eat()
    liger.sleep()

运行输出为:

Call the constructor of Tiger.
Call the constructor of Lion.
Call the constructor of Animal.
Ends the call to Animal's constructer.
Ends the call to Lion's constructer.
Ends the call to Tiger's constructer.
(<class '__main__.Liger'>, <class '__main__.Tiger'>, <class '__main__.Lion'>, <class '__main__.Animal'>, <class 'object'>)
Animal
Tiger is eating
Lion is sleeping

  • 继承顺序:通过构造函数的打印顺序和Ligermro(显示继承顺序)可以看到,继承顺序为Liger -> Tiger -> Lion -> Animal。多继承时,继承顺序一般为从左到右,从下到上。
  • 同名变量:AnimalTigerLion的初始化函数中都初始化了m_name变量,却并没有同C++中的继承冲突一样出现多份m_name的拷贝,而是只对Liger的实例liger进行动态地修改同一块内存地址,由于Animal的初始化函数最后被调用,所以m_name赋值为Animal
  • 同名函数:
    • 对于Liger的两父类TigerLion和其祖先Animal都定义过的函数eatLiger类总是选择最左父类的同名函数,所以调用Liger.eat()得到的是Tiger.eat()
    • 而如果并不是所有父类都定义过的函数,子类在类型树上从左到右寻找第一个定义过该函数的父类。例如Liger.sleep()Tiger中并未定义sleep(),所以找到了后面的Lion.sleep()

小结:对于简单的菱形继承,可以大致认为其是在类型树上按照"从左到右,从上到下"的广度优先遍历顺序查找成员的。

复杂的菱形继承

对于复杂的菱形继承,有时候按照上面的广度优先遍历类型树得到的继承顺序并不正确。例如:

这时如果使用广度优先遍历得到的继承顺序为:M A B Z X Y object

运行如下Python代码得到的继承顺序为:

class X(object):
    pass

class Y(object):
    pass


class Z(object):
    pass


class A(X, Y):
    pass


class B(Y, Z):
    pass


class M(A, B, Z):
    pass

print(M.mro())

# [<class '__main__.M'>, 
<class '__main__.A'>,
<class '__main__.X'>, 
<class '__main__.B'>,
<class '__main__.Y'>, 
<class '__main__.Z'>,
<class 'object'>]

继承顺序为M A X B Y Z object

查阅资料得知,这是因为在Python2.3以后的版本,类的继承顺序求法采用了C3算法,以保证继承的单调性原则。(子类不能改变基类的MRO搜索顺序)

MRO C3 算法

算法原理

C3(C3 linearization)算法实现保证了三种重要特性:

  • 继承拓扑图的一致性。
  • 局部优先原则。
  • 单调性原则。

在C3算法中,把L[C]定义为类C的的linearization值(也就是MRO里的继承顺序,后面简称L值),计算逻辑如下:

L[C] = C + merge of linearization of parents of C and list of parents of C in the order they are inherited from left to right. 

L[C]是所有父类的L值的merge

运算规则为:

其中 C 多继承父类 B 1 . . B N

merge的运算方法如下:

  • 对于merge的参数列表,从左到右检查每个参数的第一个元素,记为H
  • 如果H不出现在其它参数中,或者出现在某个参数中且是该参数第一个元素(头),则从所有列表中删去H并添加到C的后面形成C1。(即H不被C的所有父类继承,以保证L值的单调性)

重复上述步骤直至列表为空或者不能找出可以输出的元素。

算法例子

拿上面的继承来举例:

从上至下一次计算object,X,Y,Z,A,B,M的继承顺序:

L[object] = O(object)

L[X] = X + merge(L[object]) = X + O = XO,同理L[Y] = YOL[Z] = ZO

L[A] = A+merge(L[X],L[Y],XY)
     = A + merge(XO,YO,XY) 
	 = AX + merge(O,YO,Y)
	 = AXY + merge(O,O)
	 = AXYO

L[B] = B + merge(L[Y],L[Z],YZ)
     = B + merge(YO,ZO,YZ)
     = BY + merge(O,ZO,Z)
     = BYZ + merge(O,O)
     = BYZO

然后是M的继承顺序计算:

L[M] = M + merge(L[A],L[B],L[Z],ABZ)
     = M + merge(AXYO,BYZO,ZO,ABZ)
     = MA + merge(XYO,BYZO,ZO,BZ)
     = MAX + merge(YO,BYZO,ZO,BZ) #第一个参数中Y被第二个参数中的Z继承,所以检查第二个参数的第一个元素即 B
     = MAXB + merge(YO,YZO,ZO,Z)
     = MAXBY + merge(O,ZO,ZO,Z) #同样,O被Z继承
     = MAXBYZ + merge(O,O,O)
     = MAXBYZO

得到类M的最终继承顺序MAXBYZO

算法实现

下面是其它资料中找到的Wiki百科上对该算法的Python版本实现:

def c3MRO(cls):
    if cls is object:
        # 讨论假设顶层基类为object,递归终止
        return [object]

    # 构造C3-MRO算法的总式,递归开始
    mergeList = [c3MRO(baseCls) for baseCls in cls.__bases__]
    mergeList.append(list(cls.__bases__))
    mro = [cls] + merge(mergeList)
    return mro


def merge(inLists):
    if not inLists:
        # 若合并的内容为空,返回空list
        # 配合下文的排除空list操作,递归终止
        return []

    # 遍历要合并的mro
    for mroList in inLists:
        # 取head
        head = mroList[0]
        # 遍历要合并的mro(与外一层相同),检查尾中是否有head
        ### 此处也遍历了被取head的mro,严格地来说不符合标准算法实现
        ### 但按照多继承中地基础规则(一个类只能被继承一次),
        ### head不可能在自己地尾中,无影响,若标准实现,反而增加开销
        for cmpList in inLists[inLists.index(mroList) + 1:]:
            if head in cmpList[1:]:
                break
        else:
            # 筛选出好head
            nextList = []
            for mergeItem in inLists:
                if head in mergeItem:
                    mergeItem.remove(head)
                if mergeItem:
                    # 排除空list
                    nextList.append(mergeItem)
            # 递归开始
            return [head] + merge(nextList)
    else:
        # 无好head,引发类型错误
        raise TypeError

测试:

class A(object):pass
class B(object):pass
class C(object):pass
class E(A,B):pass
class F(B,C):pass
class G(E,F):pass

print([i.__name__ for i in c3MRO(G)])

输出结果:

['G', 'E', 'A', 'F', 'B', 'C', 'object']

参考资料

到此这篇关于一文讲解python中的继承冲突及继承顺序的文章就介绍到这了,更多相关python 继承冲突及继承顺序内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

您可能感兴趣的文章:

相关文章

  • Python 日志logging模块用法简单示例

    Python 日志logging模块用法简单示例

    这篇文章主要介绍了Python 日志logging模块用法,结合简单实例形式分析了Python 日志logging模块功能、原理及日志输出到控制台与文件的相关操作技巧,需要的朋友可以参考下
    2019-10-10
  • pycharm设置默认的UTF-8编码模式的方法详解

    pycharm设置默认的UTF-8编码模式的方法详解

    这篇文章主要介绍了pycharm设置默认的UTF-8编码模式,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • 用python画城市轮播地图

    用python画城市轮播地图

    轮播地图可以在很多方面有实际应用,所以就介绍给大家,并给出个实际操作案例,感兴趣的朋友可以参考下
    2021-05-05
  • Python Locust负载测试工具安装配置使用详解

    Python Locust负载测试工具安装配置使用详解

    本文将提供有关Python Locust的全面指南,包括安装和配置、基本概念、性能测试、任务编写、报告生成以及实际应用场景,将通过丰富的示例代码来帮助深入理解Locust的使用
    2024-01-01
  • Python处理application/json错误的方法详解

    Python处理application/json错误的方法详解

    这篇文章主要为大家详细介绍了python使用httpx_sse调用sse流式接口对响应格式为application/json的错误信息处理的相关知识,需要的可以了解下
    2025-02-02
  • 使用Python和Spire.XLS轻松实现Excel到TXT数据转换

    使用Python和Spire.XLS轻松实现Excel到TXT数据转换

    在数据处理和分析的过程中,经常需要将不同格式的数据进行转换,Excel 文件是数据存储和操作中非常常见的格式,而 TXT 文件凭借其简单的文本格式,本文将介绍如何使用 Python 和 Spire.XLS 库将 Excel 导出为 TXT 文件,需要的朋友可以参考下
    2026-02-02
  • 如何利用Python实现自动打卡签到的实践

    如何利用Python实现自动打卡签到的实践

    签到,都是规律性的操作,何尝不写一个程序加到Windows实现自动签到呢,本文就主要介绍了如何利用Python实现自动打卡签到的实践,具有一定的参考价值,感兴趣的可以了解一下
    2021-12-12
  • 解决Jupyter notebook更换主题工具栏被隐藏及添加目录生成插件问题

    解决Jupyter notebook更换主题工具栏被隐藏及添加目录生成插件问题

    这篇文章主要介绍了解决Jupyter notebook更换主题工具栏被隐藏及添加目录生成插件问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-04-04
  • python list转矩阵的实例讲解

    python list转矩阵的实例讲解

    今天小编就为大家分享一篇python list转矩阵的实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • python中DDT数据驱动的实现

    python中DDT数据驱动的实现

    DDT是一种软件测试方法,本文主要介绍了python中DDT数据驱动的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-04-04

最新评论