Python多重继承

news2024/11/24 19:11:12

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

多继承的语法结构

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

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

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

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

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

import os

class Base():
    def __init__(self):
        print(f'Base.__init__')

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()

    def foo(self):
        print(f'Base2.foo')


    pass

class Grand(Base1, Base2):
    pass

g = Grand()
g.foo()



‘’'
Base1.__init__
Base2.__init__
Base.__init__
Base1.foo
‘''

 从上面的例子可以看到:

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

甚至可以跨代混合继承:

import os

class Base():
    def __init__(self):
        print(f'Base.__init__')

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()

    def foo(self):
        print(f'Base2.foo')


    pass

class Grand(Base1, Base):
    pass

g = Grand()
g.foo()

‘’’
Base1.__init__
Base.__init__
Base1.foo
’‘’

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

import os

class Base():
    def __init__(self):
        print(f'Base.__init__')

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()

    def foo(self):
        print(f'Base2.foo')


    pass

class Grand(Base, Base2):
    pass

g = Grand()
g.foo()


‘’’
TypeError: Cannot create a consistent method resolution
order (MRO) for bases Base, Base2
’‘’

MRO

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

import os

class Base():
    def __init__(self):
        print(f'Base.__init__')

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()

    def foo(self):
        print(f'Base2.foo')

class Base3(Base):
    def __init__(self):
        print(f'Base3.__init__')
        super().__init__()

    def foo(self):
        print(f'Base3.foo')

class Base31(Base1, Base2):
    pass

class Base32(Base2, Base3):
    pass

class Grand(Base32, Base31):
    pass

g = Grand()
g.foo()
print(Grand.mro())

‘’’
Base1.__init__
Base2.__init__
Base3.__init__
Base.__init__
Base1.foo
[<class '__main__.Grand'>, <class '__main__.Base32'>, <class '__main__.Base31'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.Base3'>, <class '__main__.Base'>, <class 'object'>]
’‘’

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

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

旧式类MRO算法

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

class A:
    def method(self):
        print("CommonA")
class B(A):
    pass
class C(A):
    def method(self):
        print("CommonC")
class D(B, C):
    pass

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 搜索顺序,否则会导致程序发生异常。

例如:

class X(object):
    pass
class Y(object):
    pass
class A(X,Y):
    pass
class B(Y,X):
    pass
class C(A, B):
    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() 内置函数(对象)返回一个代理对象(超类的临时对象),允许我们访问基类的方法。

class Person(object):
    def eat(self, times):
        print(f'我每天吃{times}餐。')

class Student(Person):
    def eat(self):
        # 调用超类
        super().eat(4)

tom = Student()
tom.eat()
# 我每天吃4餐。

Student.mro()
# [__main__.Student, __main__.Person, object]

调用父类初始化继承:

class Base(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

class A(Base):
    def __init__(self, a, b, c):
        super().__init__(a, b)
        #super(A, self).__init__(a, b)  # Python2 写法
        self.c = c


a = A(1,2,3)
A.mro()
# [__main__.A, __main__.Base, object]

语法

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

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

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

import os

class Base():
    def __init__(self):
        print(f'Base.__init__')

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()

    def foo(self):
        print(f'Base2.foo')


    pass

class Grand(Base1, Base2):
    def __init__(self):
        print(f'Grand.__init__')
        super().__init__()

    def foo(self):
        print(f'Grand.foo')
        super(Base1, self).foo()

g = Grand()
g.foo()
print(Grand.mro())


‘’'
Grand.__init__
Base1.__init__
Base2.__init__
Base.__init__
Grand.foo
Base2.foo
[<class '__main__.Grand'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.Base'>, <class 'object'>]
‘''

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() 并不限于在方法内部使用。 两个参数的形式明确指定参数并进行相应的引用。 零个参数的形式仅适用于类定义内部,因为编译器需要填入必要的细节以正确地检索到被定义的类,还需要让普通方法访问当前实例。

import os

class Base():
    def __init__(self):
        print(f'Base.__init__')

    def foo(self):
        print(f'Base.foo')


class Base1(Base):
    def __init__(self):
        print(f'Base1.__init__')
        super().__init__()

    def foo(self):
        print(f'Base1.foo')

class Base2(Base):
    def __init__(self):
        print(f'Base2.__init__')
        super().__init__()
        self.__nicename__ = 'Base2'

    def foo(self):
        print(f'Base2.foo')


    pass

class Grand(Base1, Base2):
    def __init__(self):
        print(f'Grand.__init__')
        super().__init__()

    def foo(self):
        print(f'Grand.foo')
        super(Base1, self).foo()
        print('__getattribute__:', super().__getattribute__('__nicename__'))

g = Grand()
g.foo()
print(Grand.mro())

‘’'
Grand.__init__
Base1.__init__
Base2.__init__
Base.__init__
Grand.foo
Base2.foo
__getattribute__: Base2
[<class '__main__.Grand'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.Base'>, <class 'object'>]
‘''

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1036916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 判断回文数

"""判断输入的数是否为回文数介绍&#xff1a;回文数&#xff1a;数字从高位到低位正序排列和低位到高位逆序排列都是同一数值例如&#xff1a;数字 1221 无论正序还是逆序都是 1221知识点&#xff1a;1、获取字符串长度函数len()2、条件语句if/elif/else3、循环…

MySQL 高级(进阶) SQL 语句(二) -----存储过程

目录 1 存储过程 1.1 创建存储过程​ 1.2 调用存储过程 1.3 查看存储过程 1.4 存储过程的参数 1.5 修改存储过程 1.6 删除存储过程 2 条件语句 3 循环语句 1 存储过程 存储过程是一组为了完成特定功能的SQL语句集合。 存储过程在使用过程中是将常用或者复杂的工作预…

常用的软件项目管理工具有哪些?

在软件项目管理中&#xff0c;项目计划是工作中非常重要的一环&#xff0c;因此在选择软件项目管理工具时&#xff0c;除了任务管理、进度跟踪外&#xff0c;还需要关注软件的项目计划能力。 软件项目管理的工具有哪些&#xff1f;有什么好用的软件项目管理工具吗&#xff1f;…

windows 深度学习环境部署

1. 根据显卡配置安装适合的CUDA,查看显卡配置可在显卡控制面板上查看,安装是否成功可通过nvidia-smi查看&#xff1b;注意安装路径 https://developer.nvidia.com/cuda-toolkit-archive 2. 根据cuda安装合适的cudnn&#xff0c;需要注册NVIDIA cuDNN Archive | NVIDIA Devel…

记录一次关于嵌套事务传播机制的bug

1、具体问题 这段代码是A嵌套B&#xff0c;B嵌套C&#xff0c;遇到的bug是C代码发生异常进行事务标记&#xff08;因为使用的传播行为是默认的REQUIRED所以要等A事务进行rollback,C加入了A事务只能暂时进行标记&#xff09;,但是由于B代码进行了异常捕获返回给了A信息并没有抛…

[C]精炼分析状态机FSM

FSM&#xff1a;finite state machine 【有限状态机】&#xff0c;用通俗的语言来表达就是逻辑流程图。 当前状态满足触发条件时&#xff0c;就会切换到下一个状态&#xff0c;并执行对应的任务操作。传统代码做法是用if-else 或者 switch-case来处理。若要做到可扩展性良好的…

【车联网/自动驾驶仿真学习】VEINS_CARLA安装指南

VEINS_CARLA安装指南 这是Veins团队开发的一个接口&#xff0c;能够实现veins和carla之间的数据传输&#xff0c;通过veins通信模块能够实现carla中感知决策等相关数据的传输。 github:veins_carlapaper:Poster: A Case for Heterogenous Co-Simulation of Cooperative and A…

MySQL 高级(进阶) SQL 语句(二) -----连接查询、union联集、case、正则表达式

目录 1 连接查询 1.1 内连接 1.2 左连接 1.3 右连接 2 UNION ----联集 2.1 交集值 2.2 无交集值 3 case 4 正则表达式 1 连接查询 准备工作&#xff1a; create database k1; use k1; create table location (Region char(20),Store_Name char(20)); insert into loca…

最新时间注入攻击和代码分析技术

点击星标&#xff0c;即时接收最新推文 本文选自《web安全攻防渗透测试实战指南&#xff08;第2版&#xff09;》 点击图片五折购书 时间注入攻击 时间注入攻击的测试地址在本书第2章。 访问该网址时&#xff0c;页面返回yes&#xff1b;在网址的后面加上一个单引号&#xff0c…

《从菜鸟到大师之路 Nginx 篇》

《从菜鸟到大师之路 Nginx 篇》 Nginx 简介 Nginx 是开源、高性能、高可靠的 Web 和反向代理服务器&#xff0c;而且支持热部署&#xff0c;几乎可以做到 7 * 24 小时不间断运行&#xff0c;即使运行几个月也不需要重新启动&#xff0c;还能在不间断服务的情况下对软件版本进行…

java面试题-学成在线项目

1、详细说说你的项目吧 从以下几个方面进行项目介绍&#xff1a; 1、项目的背景&#xff0c;包括&#xff1a;是自研还是外包、什么业务、服务的客户群是谁、谁去运营等问题。 2、项目的业务流程 3、项目的功能模块 4、项目的技术架构 5、个人工作职责 6、个人负责模块的详细说…

R统计绘图-线性混合效应模型详解(理论、模型构建、检验、选择、方差分解及结果可视化)

目录 一、 基础理论 二、数据准备 三、构建线性混合效应模型(LMMs) 3.1 lme4线性混合效应模型formula 3.2 随机截距模型构建及检验 3.3 随机截距模型分析结果解释及可视化 3.4 随机斜率模型构建、检验及可视化 四、线性混合效应模型选择 4.1 多模型比较 4.2 模型最优子…

003-第一代硬件系统环境搭建

第一代硬件系统环境搭建 文章目录 第一代硬件系统环境搭建项目介绍摘要结构部分电路部分软件部分 关键字&#xff1a; Qt、 Qml、 硬件、 系统、 搭建 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&#xff08;Qt Meta-Object Language&#…

Java编程的精髓:深入理解JVM和性能优化

文章目录 Java虚拟机&#xff08;JVM&#xff09;的核心概念1. 类加载器&#xff08;Class Loader&#xff09;2. 内存区域3. 垃圾回收&#xff08;Garbage Collection&#xff09;4. 类型转换和多态 JVM性能调优1. JVM参数调整2. 内存管理3. 多线程优化4. 使用性能分析工具5. …

服务注册发现_创建服务消费者

创建cloud-consumer-order80模块 pom文件添加依赖 <dependencies><!-- 引入Eureka client依赖 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId&…

CompletableFuture-FutureTask结合线程池提升性能

使用线程池&#xff1a; 返回计算结果&#xff1a; 2.2.3 Future编码实战和优缺点分析 优点&#xff1a;Future线程池异步多线程任务配合&#xff0c;能显著提高程序的运行效率。 缺点&#xff1a; get()阻塞---一旦调用get()方法求结果&#xff0c;一旦调用不见不散&…

GEE:哨兵时间序列遥感数据和动态阈值方法计算物候时期EOS/SOS(2)

作者:CSDN @ _养乐多_ 本文将分享和解释论文《Improved Estimates of Arctic Land Surface Phenology Using Sentinel-2 Time Series》中使用到的基于阈值估算北极地区的植被物候,特别是北极地区的植被季节开始和结束的日期(SoS和EoS)的方法和代码。该方法使用的是使用哨兵…

Pytorch(GPU)环境安装

winR:启动cmd; 输入nvidia-smi 查看cuda的配置 (1) 安装CUDA 地址&#xff1a;https://developer.nvidia.com/cuda-downloads 详细参考&#xff1a;安装CUDA与CUDNN与Pytorch&#xff08;最新超级详细图文版本2023年8月最新&#xff09;_pytorch安装cudnn_LyaJpunov的博客-C…

Zookeeper-JavaApI操作

JavaApI操作 JavaApI操作1) Curator 介绍2) Curator API 常用操作a) 建立连接与CRUD基本操作b) Watch事件监听c) 分布式锁c.1) 介绍c.2) Zookeeper分布式锁原理c.3) 案例&#xff1a;模拟12306售票 JavaApI操作 1) Curator 介绍 Curator 是 Apache ZooKeeper 的Java客户端库。…

树结构的讲解与二叉树的基本运用

目录&#xff1a; 一&#xff0c;树的基本知识 二&#xff0c;树的类型 三&#xff0c;树的存储 四&#xff0c;树的基本运算 五&#xff0c;二叉树堆的基本运用 一&#xff0c;树的基本知识 树是一种非线性的数据结构&#xff0c;它是由n个有限结点组合而成为一个具有层次…