当前位置:首页 > 物联网 > Freak嵌入式

摘要:

本文主要介绍了Python中创建自定义类时如何使用多重继承、菱形继承的概念和易错点,同时讲解了如何使用PyQtGraph库对串口接收的数据进行绘图。

文档和代码获取:

本文档主要介绍如何使用 Python 进行面向对象编程,需要读者对 Python 语法和单片机开发具有基本了解。相比其他讲解 Python 面向对象编程的博客或书籍而言,本文档更加详细、侧重于嵌入式上位机应用,以上位机和下位机的常见串口数据收发、数据处理、动态图绘制等为应用实例,同时使用 Sourcetrail代码软件对代码进行可视化阅读便于读者理解。

正文

在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 则可以确定超类之间的初始化顺序。


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭