全网最适合入门的面向对象编程教程:18 类和对象的 Python 实现-多重继承与 PyQtGraph 串口数据绘制曲线图
全网最适合入门的面向对象编程教程:18 类和对象的 Python 实现-多重继承与 PyQtGraph 串口数据绘制曲线图
摘要:
本文主要介绍了 Python 中创建自定义类时如何使用多重继承、菱形继承的概念和易错点,同时讲解了如何使用 PyQtGraph 库对串口接收的数据进行绘图。
原文链接:
往期推荐:
全网最适合入门的面向对象编程教程:00 面向对象设计方法导论
全网最适合入门的面向对象编程教程:01 面向对象编程的基本概念
全网最适合入门的面向对象编程教程:02 类和对象的 Python 实现-使用 Python 创建类
全网最适合入门的面向对象编程教程:03 类和对象的 Python 实现-为自定义类添加属性
全网最适合入门的面向对象编程教程:04 类和对象的Python实现-为自定义类添加方法
全网最适合入门的面向对象编程教程:05 类和对象的Python实现-PyCharm代码标签
全网最适合入门的面向对象编程教程:06 类和对象的Python实现-自定义类的数据封装
全网最适合入门的面向对象编程教程:07 类和对象的Python实现-类型注解
全网最适合入门的面向对象编程教程:08 类和对象的Python实现-@property装饰器
全网最适合入门的面向对象编程教程:09 类和对象的Python实现-类之间的关系
全网最适合入门的面向对象编程教程:10 类和对象的Python实现-类的继承和里氏替换原则
全网最适合入门的面向对象编程教程:11 类和对象的Python实现-子类调用父类方法
全网最适合入门的面向对象编程教程:12 类和对象的Python实现-Python使用logging模块输出程序运行日志
全网最适合入门的面向对象编程教程:13 类和对象的Python实现-可视化阅读代码神器Sourcetrail的安装使用
全网最适合入门的面向对象编程教程:全网最适合入门的面向对象编程教程:14 类和对象的Python实现-类的静态方法和类方法
全网最适合入门的面向对象编程教程:15 类和对象的 Python 实现-__slots__魔法方法
全网最适合入门的面向对象编程教程:16 类和对象的Python实现-多态、方法重写与开闭原则
全网最适合入门的面向对象编程教程:17 类和对象的Python实现-鸭子类型与“file-like object“
更多精彩内容可看:
给你的 Python 加加速:一文速通 Python 并行计算
一个MicroPython的开源项目集锦:awesome-micropython,包含各个方面的Micropython工具库
文档和代码获取:
可访问如下链接进行对文档下载:
https://github.com/leezisheng/Doc

本文档主要介绍如何使用 Python 进行面向对象编程,需要读者对 Python 语法和单片机开发具有基本了解。相比其他讲解 Python 面向对象编程的博客或书籍而言,本文档更加详细、侧重于嵌入式上位机应用,以上位机和下位机的常见串口数据收发、数据处理、动态图绘制等为应用实例,同时使用 Sourcetrail 代码软件对代码进行可视化阅读便于读者理解。
相关示例代码获取链接如下:https://github.com/leezisheng/Python-OOP-Demo
正文
在 python 中一个类能继承自不止一个父类,这叫做 python 的多重继承,多重继承的语法与单继承类似:
class SubclassName(BaseClass1, BaseClass2, BaseClass3, ...):
pass
当然,子类所继承的所有父类同样也能有自己的父类,这样就可以得到一个继承关系机构图如下图所示:

多重继承最常见的用途就是用于创建包含两组完全不同行为的对象。例如,设计一个对象用于连接扫描器并将扫描的文件通过传真发送出去,这一对象可能继承自两个完全独立的 scanner 和 faxer 对象。
对于 MasterClass 来说,我们希望它可以具有绘图功能,能够将串口接收到的传感器数据动态绘制曲线,这里我们借助 PyQtGraph 库来完成,PyQtGraph 是纯 Python 图形 GUI 库,它充分利用 PyQt 和 PtSide 的高质量的图形表现水平和 NumPy 的快速科学计算与处理能力,在数学、科学和工程领域都有广泛的应用。
PyQtGraph 相比于 matplotlib 更加适合于数据采集和分析应用。
我们使用如下两条语句完成 PyQtGraph 库和其依赖库 PyQt5 的安装:
**pip install pyqtgraph **
**pip install PyQt5 **
pip install numpy
这里,我们首先定义一个绘图类及其方法,示例代码如下:
class PlotClass:
_# 绘图类初始化_
def __init__(self,wintitle:str="Basic plotting examples",plottitle:str="Updating plot",width:int=1000,height:int=600):
_# Qt应用实例对象_
self.app = None
_# 窗口对象_
self.win = None
_# 设置窗口标题_
self.title = wintitle
_# 设置窗口尺寸_
self.width = width
self.height = height
_# 传感器数据_
self.value = 0
_# 计数变量_
self.__count = 0
_# 传感器数据缓存列表_
self.valuelist = []
_# 绘图曲线_
self.curve = None
_# 图层对象_
self.plotob = None
_# 图层标题_
self.plottitle = plottitle
_# Qt应用和窗口初始化_
self.appinit()
_# 应用程序初始化_
def appinit(self):
_# 创建一个Qt应用,并返回该应用的实例对象_
self.app = pg.mkQApp("Plotting Example")
_# 生成多面板图形_
_# show:(bool) 如果为 True,则在创建小部件后立即显示小部件。_
_# title:(str 或 None)如果指定,则为此小部件设置窗口标题。_
self.win = pg.GraphicsLayoutWidget(show=True, title=self.title)
_# 设置窗口尺寸_
self.win.resize(self.width, self.height)
_# 进行窗口全局设置,setConfigOptions一次性配置多项参数_
_# antialias启用抗锯齿,useNumba对图像进行加速_
pg.setConfigOptions(antialias=True, useNumba=True)
_# 添加图层_
self.plotob = self.win.addPlot(title=self.plottitle)
_# 添加曲线_
self.curve = self.plotob.plot(pen='y')
_# 接收数据_
def GetValue(self,value):
self.value = value
_# 加入数据缓存列表_
self.valuelist.append(value)
_# 更新曲线数据_
def DataUpdate(self):
_# 模拟绘制正弦曲线_
_# 计数变量更新_
self.__count = self.__count + 0.1
self.value = np.sin(self.__count)
self.GetValue(self.value)
_# 将数据转化为图形_
self.curve.setData(self.valuelist)
_# 设置定时更新_
def SetUpdate(self,time:int = 100):
_# 创建定时器对象_
timer = QtCore.QTimer()
_# 定时器结束,触发DataUpdate方法_
timer.timeout.connect(self.DataUpdate)
_# 启动定时器_
timer.start(time)
_# 进入主事件循环并等待_
pg.exec()
if __name__ == '__main__':
_# 创建PlotClass对象,自动完成初始化_
p = PlotClass()
_# 设置定时更新任务_
p.SetUpdate()
这里,我们定义了如下属性和方法:
| 属性/方法 | 作用 |
|---|---|
| wintitle | 窗口标题 |
| plottitle | 图层标题 |
| width | 窗口宽度 |
| height | 窗口高度 |
| app | Qt 应用实例对象 |
| win | 窗口对象 |
| value | 传感器数据 |
| __count | 计数变量 |
| valuelist | 传感器数据缓存列表 |
| curve | 绘图曲线 |
| plotob | 图层对象 |
| appinit(self) | 用于 qt 应用程序初始化,添加窗口、曲线和图层 |
| GetValue(self,value) | 用于接收传感器数据,加入缓存列表 |
| DataUpdate(self) | 用于定时进行曲线更新,这里模拟绘制正弦曲线 |
| SetUpdate(self,time:int = 100) | 设置定时更新任务 |
首先,我们在__init__和 appinit()中完成初始化操作,包括对类内属性、Qt 应用实例、窗口图层及曲线的初始化:

接着我们在 SetUpdate 方法中创建定时器对象并设置定时任务,当设置的延时时间到达时,调用 DataUpdate 方法,在其中对数据曲线并进行更新,示例中,我们利用__count 属性每次递增,使得其绘制正弦曲线。
同时设置进入主事件循环并等待吗,以使得 Qt 界面保持显示:

这里,我们在主函数中创建对象并启动运行定时刷新曲线,如下为结果:

这里,我们想要使得 MasterClass 类同时具备串口收发和绘图功能,因此要用到多重继承,MasterClass 类同时继承于 SerialClass 和 PlotClass。通过多重继承,一个子类就可以同时获得多个父类的所有功能。
示例代码如下:
class MasterClass(SerialClass,PlotClass):
_# 类变量:_
_# BUSY_STATE -忙碌状态-0_
_# IDLE_STATE -空闲状态-1_
BUSY_STATE, IDLE_STATE = (0, 1)
_# 类变量:_
_# START_CMD - 开启命令 -0_
_# STOP_CMD - 关闭命令 -1_
_# SENDID_CMD - 发送ID命令 -2_
_# SENDVALUE_CMD - 发送数据命令 -3_
START_CMD, STOP_CMD, SENDID_CMD, SENDVALUE_CMD = (0, 1, 2, 3)
_# 类的初始化_
def __init__(self,state:int = IDLE_STATE,port:str = "COM17",wintitle:str="Basic plotting examples",plottitle:str="Updating plot",width:int=1000,height:int=600):
_# 分别调用不同父类的__init__方法_
SerialClass.__init__(self,port)
PlotClass.__init__(self,wintitle,plottitle,width,height)
self.valuequeue = queue.Queue(10)
self.__masterstatue = state
_# 初始化完成的标志量_
self.INIT_FLAG = False
@classmethod
def MasterInfo(cls):
print("Info : "+str(cls))
_# 开启主机_
def StartMaster(self):
super().OpenSerial()
logging.info("START MASTER :"+self.dev.port)
_# 停止主机_
def StopMaster(self):
super().CloseSerial()
logging.info("CLOSE MASTER :" + self.dev.port)
_# 接收传感器ID号_
def RecvSensorID(self):
sensorid = super().ReadSerial()
logging.info("MASTER RECIEVE ID : " + str(sensorid))
return sensorid
_# 接收传感器数据_
def RecvSensorValue(self):
data = super().ReadSerial()
logging.info("MASTER RECIEVE DATA : " + str(data))
self.valuequeue.put(data)
return data
_# 主机发送命令_
def SendSensorCMD(self,cmd):
super().WriteSerial(str(cmd))
logging.info("MASTER SEND CMD : " + str(cmd))
_# 主机返回工作状态-_
def RetMasterStatue(self):
return self.__masterstatue
_# 重写父类的DataUpdate方法_
def DataUpdate(self):
self.SendSensorCMD(self.SENDVALUE_CMD)
self.value = self.RecvSensorValue()
self.WriteSerial("Recv:"+str(self.value))
self.GetValue(self.value)
self.curve.setData(self.valuelist)
if __name__ == "__main__":
_# 初始化对象_
m = MasterClass(state = MasterClass.IDLE_STATE,
port = "COM17",
wintitle = "Basic plotting examples",
plottitle = "Updating plot",
width = 1000,
height = 600)
m.StartMaster()
m.SendSensorCMD(MasterClass.SENDID_CMD)
m.RecvSensorID()
_# 设置定时更新任务_
m.SetUpdate()
我们可以看到两个父类和子类关系及不同类的属性和方法如下:

首先,我们使用如下语句表明 MasterClass 继承于 SerialClass 和 PlotClass:
class MasterClass(SerialClass,PlotClass):
接着,我们在 MasterClass 的初始化方法中分别调用不同父类的__init__方法:
SerialClass.__init__(self,port)
PlotClass.__init__(self,wintitle,plottitle,width,height)
同时我们在 MasterClass 中重写父类的 DataUpdate 方法,首先发送查询数据指令,接着等待接收数据,完成数据接收后发送接收到的数据并存入数据缓存列表,在设置定时任务后,定时更新曲线。

如下为运行效果,我们可以看到接收到数据后正常完成曲线的更新:

在测试过程中,我们可以看到 Qt 窗口会有无法响应的情况出现,这是由于界面主线程是单线程,如果在 UI 主线程中执行耗时操作,例如点击按钮,响应函数去数据库查询数据,数据量比较大时,查询需要几秒钟甚至几十秒的时间,如果 UI 主线程一直等待响应函数返回,阻塞在响应函数内部,就无法响应界面的其他消息或者事件,界面就会卡死,无响应。这种情况可以利用 Python 的多线程或多进程得以避免,这个情况将在后面讲到。
可以看到,在创建包含两组完全不同行为的对象的情况下,两个类接口不同,子类完全可以正常运行,但是如果两个类的接口有重叠,同时继承就可能造成混乱。最好的方法就是避免这种情况,重新分析系统,看看是否能够去掉多重继承关系并用其他的关联或组合设计替代。
同时切记,尽量不要在子类的初始化方法中手动调用父类对象的初始化方法,会导致导致菱形继承无法被正确处理,尽量使用 Python 内置的 super() 函数,并且为 Python 类规定了标准的方法解析顺序 MRO 。使用 super() 函数初始化父类可以确保菱形继承体系中的共同超类只初始化一次。MRO 则可以确定超类之间的初始化顺序。
关于多重继承中调用同名方法时的具体情况和调用顺序可以查看如下链接:
https://pythonhowto.readthedocs.io/zh-cn/latest/object.html#id29

全网最适合入门的面向对象编程教程:18 类和对象的 Python 实现-多重继承与 PyQtGraph 串口数据绘制曲线图的更多相关文章
- Java基础--面向对象编程1(类与对象)
1.类(class)的定义 类是对一组具有相同特征和行为的对象的抽象描述. 在程序中,引入类的概念,就是为了快速生成更多的具有相同特性和行为的事物. 2.对象(object)的定义 对象是类的具体实现 ...
- Python开发基础-Day17面向对象编程介绍、类和对象
面向对象变成介绍 面向过程编程 核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西.主要应用在一旦完成很少修改的地方,如linux ...
- python基础之面向对象编程介绍、类和对象
面向对象变成介绍 面向过程编程 核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西.主要应用在一旦完成很少修改的地方,如linux ...
- [Java入门笔记] 面向对象编程基础(二):方法详解
什么是方法? 简介 在上一篇的blog中,我们知道了方法是类中的一个组成部分,是类或对象的行为特征的抽象. 无论是从语法和功能上来看,方法都有点类似与函数.但是,方法与传统的函数还是有着不同之处: 在 ...
- 最适合入门的Laravel中级教程(一)
Laravel 是一个全栈框架: 我们使用 Laravel 开发业务常见有 3 个方向: 前端页面和后端逻辑混合的应用 主要是面向对 SEO 有需求的项目: 比如说新闻资讯博客文章等: 一般在控制器中 ...
- Python:面向对象编程3 定制类(有更新)
Python:面向对象编程3 定制类(有更新) ⚠️本文主要内容为对Data model相关知识点的提取学习记录.(内容来自文档和部分网页教程案例) ⚠️:这个连接指向<流畅的python&g ...
- [.net 面向对象编程基础] (9) 类和类的实例
[.net 面向对象编程基础] (9) 类和类的实例 类 ,顾名思义就是分类.类别的意思.我们要面向对象编程,就需要对不同的事物进行分类.类可以说是.net面向对象的核心. 类:就是具有相同的属性和功 ...
- [.net 面向对象编程基础] (10) 类的成员(字段、属性、方法)
[.net 面向对象编程基础] (10) 类的成员(字段.属性.方法) 前面定义的Person的类,里面的成员包括:字段.属性.方法.事件等,此外,前面说的嵌套类也是类的成员. a.类的成员为分:静态 ...
- [.net 面向对象编程基础] (18) 泛型
[.net 面向对象编程基础] (18) 泛型 上一节我们说到了两种数据类型数组和集合,数组是指包含同一类型的多个元素,集合是指.net中提供数据存储和检索的专用类. 数组使用前需要先指定大小,并且检 ...
- Python入门之面向对象编程(一)面向对象概念及优点
概念 谈到面向对象,很多程序员会抛出三个词:封装.继承和多态:或者说抽象.一切都是对象之类的话,然而这会让初学者更加疑惑.下面我想通过一个小例子来说明一下 面向对象一般是和面向过程做对比的,下面是一个 ...
随机推荐
- 超详细--redis在Linux环境搭建主从复制
引言Redis是一个高性能的缓存中间件,一个Redis服务器可以支撑很多的并发请求.但是在一些超高的并发场景下,虽然Redis读写速度很快,但也会产生读写压力过大,服务器负载过高的情况.为了分担读写的 ...
- 如何启动?win11下的Linux子系统【4种方法】
实验室的开发环境在Linux操作系统下,时不时就需要打开Linux环境去操作,而且需要本地编译或者远程SSH.这时候window和Linux切换很不方便.本科的做法就是window+虚拟机的Linux ...
- 开发中你不得不知的一个Git小技巧
一. 背景 在工作中大家应会碰到需要频繁在两个分支中切换工作的情况,我们通常做法是利用git stash命令暂存当前工作区中的变更,然后git checkout到目标分支中工作,工作完成后回到刚刚分支 ...
- volatile关键字到底有什么作用
提示:更多优秀博文请移步博主的GitHub仓库:GitHub学习笔记.Gitee学习笔记 volatile是Java提供的一种轻量级的同步机制.Java 语言包含两种内在的同步机制:同步块(或方法)和 ...
- Android OpenMAX(一)漫谈
在开始正式的学习前,我们先来聊一聊Android音视频开发中的一些问题.感受与想法.(有一点要事先说明,我的问题与答案.想法并不一定正确,请读者带着审慎的思考来阅读,后续的文章也是一样,希望读者边阅读 ...
- WebView2在WPF中的应用
开发环境 运行环境:.Net 6 开发环境:Visual Studio 2022 17.1.3 框架语言:WPF 安装WebView2 通过Package Manager控制台安装 Install-P ...
- 鸿蒙极速入门(五)-路由管理(Router)
页面路由指在应用程序中实现不同页面之间的跳转和数据传递.HarmonyOS提供了Router模块,通过不同的url地址,可以方便地进行页面路由,轻松地访问不同的页面. 一.基础使用 Router模块提 ...
- 16位简单ASM题的记录——[HGAME 2022 week1]easyasm
第一次遇见16位,和纯看汇编的题目,记录一下 DIE 16位,IDA用32位或者64位都可以打开 IDA 主要汇编部分 seg003:0000 ; =============== S U B R O ...
- Qt初始化代码基本说明
参考视频:黑马程序员https://www.bilibili.com/video/BV1XW411x7NU?p=4 1 工程基本介绍 按照这个基本步骤(https://www.cnblogs.com ...
- linux,curl命令发送各类请求详解
当你经常面对api时,curl将是你重要学习的工具,因为curl可以让你不需要浏览器也能作为Http客户端发送请求.而且它是跨平台的,Linux.Windows.Mac都会执行的很好. 一.curl ...