全網最適合入門的物件導向程式設計教程:10 類和物件的 Python 實現-類的繼承和里氏替換原則,Python 模擬主機和感測器自定義類
摘要:
本文主要介紹了類的繼承的基本概念和里氏替換原則,以模擬感測器資料串列埠輸出-上位機串列埠接收為例,對工作流程、工作模式和基本概念進行講解,同時建立了主機類和感測器類,定義了屬性和抽象方法。
原文連結:
FreakStudio 的部落格
往期推薦:
學嵌入式的你,還不會物件導向??!
全網最適合入門的物件導向程式設計教程:00 物件導向設計方法導論
全網最適合入門的物件導向程式設計教程:01 物件導向程式設計的基本概念
全網最適合入門的物件導向程式設計教程:02 類和物件的 Python 實現-使用 Python 建立類
全網最適合入門的物件導向程式設計教程:03 類和物件的 Python 實現-為自定義類新增屬性
全網最適合入門的物件導向程式設計教程:04 類和物件的Python實現-為自定義類新增方法
全網最適合入門的物件導向程式設計教程:05 類和物件的Python實現-PyCharm程式碼標籤
全網最適合入門的物件導向程式設計教程:06 類和物件的Python實現-自定義類的資料封裝
全網最適合入門的物件導向程式設計教程:07 類和物件的Python實現-型別註解
全網最適合入門的物件導向程式設計教程:08 類和物件的Python實現-@property裝飾器
全網最適合入門的物件導向程式設計教程:09 類和物件的Python實現-類之間的關係
更多精彩內容可看:
給你的 Python 加加速:一文速通 Python 平行計算
一文搞懂 CM3 微控制器除錯原理
肝了半個月,嵌入式技術棧大彙總出爐
電子計算機類比賽的“武林秘籍”
一個MicroPython的開源專案集錦:awesome-micropython,包含各個方面的Micropython工具庫
文件和程式碼獲取
可訪問如下連結進行對文件下載:
https://github.com/leezisheng/Doc
本文件主要介紹如何使用 Python 進行物件導向程式設計,需要讀者對 Python 語法和微控制器開發具有基本瞭解。相比其他講解 Python 物件導向程式設計的部落格或書籍而言,本文件更加詳細、側重於嵌入式上位機應用,以上位機和下位機的常見串列埠資料收發、資料處理、動態圖繪製等為應用例項,同時使用 Sourcetrail 程式碼軟體對程式碼進行視覺化閱讀便於讀者理解。
相關示例程式碼獲取連結如下:https://github.com/leezisheng/Python-OOP-Demo
正文
類的繼承的基本概念
剛才我們提到了,可以在已有類的基礎上建立新類,這其中的一種做法就是讓一個類從另一個類那裡將屬性和方法直接繼承下來,從而減少重複程式碼的編寫。提供繼承資訊的我們稱之為父類,也叫超類或基類;得到繼承資訊的我們稱之為子類,也叫派生類或衍生類。子類除了繼承父類提供的屬性和方法,還可以定義自己特有的屬性和方法,所以子類比父類擁有的更多的能力,在實際開發中,我們經常會用子類物件去替換掉一個父類物件,這是物件導向程式設計中一個常見的行為,對應的原則稱之為里氏替換原則。
里氏替換原則
所謂里氏替換原則通俗來說就是子類可以擴充套件父類的功能,但不能改變父類原有的功能。有以下幾個引申含義:
- (1)子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法。
- (2)子類中可以增加自己特有的方法。
- (3)當子類的方法過載父類的方法時,方法的前置條件(方法的輸入,入參)要比父類的入參更寬鬆。
- (4)當子類的方法實現父類的方法時(重寫,過載,實現抽象方法),方法的後置條件(輸出、返回值)要比父類更嚴格或相等。
更多相關資訊可以看:https://www.jyt0532.com/2020/03/22/lsp/
應用例項的抽象實現
這裡,我們以模擬感測器資料串列埠輸出-上位機串列埠接收為例,進行講解:在現在的開發中,許多感測器都內部整合了 ADC 和 MCU 晶片,可以將採集到的感測器測量的物理量對應的電壓量進行轉換並進行濾波等處理後,將更精確的感測器資料透過串列埠進行輸出。這裡,感測器和上位機間關於串列埠通訊的部分就可以抽象為串列埠類,二者都具有串列埠的收發功能。但感測器類和上位機接收類與串列埠類有有所不同:
SensorClass 類 | |
---|---|
類內成員 | 作用 |
屬性 attribute:sensorvalue | 感測器採集的資料 |
屬性 attribute:sensorid | 感測器 ID 號,用於識別是哪個感測器 |
** **** **屬性 attribute:sensorstate | 感測器工作狀態,包括:** ① 被動響應模式(預設):主機傳送查詢資料指令後,感測器傳送資料;**** ② 輪詢工作模式:間隔 100ms 傳送一個感測器資料,輪詢工作;** |
方法 operation:SendSensorID | 傳送感測器 ID 號 |
方法 operation:SendSensorValue | 傳送感測器採集的資料 |
方法 operation:RecvMasterCMD | 接收主機指令 |
方法 operation:StartSensor | 開始感測器工作 |
方法 operation:StopSensor | 停止感測器工作 |
方法 operation:InitSensor | 初始化感測器 |
工作流程簡單來說就是感測器初始化後開始工作,不斷採集資料,接收到主機傳送的查詢資料命令後,傳送感測器資料;對應的接收到主機傳送的不同指令後執行對應操作。
我們來看如下示例程式碼,SensorClass 類具體方法實現先省略:
class SensorClass(SerialClass):
_# 類的初始化_
def __init__(self,id,state):
_# 呼叫父類的初始化方法,super() 函式將父類和子類連線_
super().__init__()
self.sensorvalue = 0
self.sensorid = id
self.sensorstate = state
_# 感測器上電初始化_
def InitSensor(self):
pass
_# 開啟感測器_
def StartSensor(self):
pass
_# 停止感測器_
def StopSensor(self):
pass
_# 傳送感測器ID號_
def SendSensorID(self):
pass
_# 傳送感測器資料_
def SendSensorValue(self):
pass
這裡,繼承語法為:
class 派生類名(基類名)
...
在 SensorClass 類的初始化方法中,我們首先呼叫如下語句:
super().__init__()
這裡,super()用來呼叫父類(基類)的方法,init()是類的構造方法, super().init() 就是呼叫父類的 init 方法, 同樣可以使用 super()去呼叫父類的其他方法。這裡,你可能會問,我們為何不用如下語句來初始化父類 SerialClass 呢?
SerialClass.__init__()
這看起來多清晰、多方便。實際上,super() 是用來解決多重繼承問題的,直接用類名呼叫父類方法在使用單繼承的時候沒問題,但是如果使用多繼承,會涉及到查詢順序(MRO)、重複呼叫(鑽石繼承)等種種問題。這裡,不對鑽石繼承問題做過多敘述,只需知道子類初始化時必須呼叫 super() 方法。
有關鑽石繼承問題可以檢視如下連結:
https://blog.csdn.net/qq_41556318/article/details/84521836
對於主機的 MasterClass 類來說,除了繼承於父類 SerialClass 的屬性和方法外,還包括其他屬性和方法,如下講解:
MasterClass 類 | |
---|---|
類內成員 | 作用 |
屬性 attribute:valuequeue | 主機中感測器採集資料的快取佇列 |
屬性 attribute:masterstate | 主機狀態,包括:① 空閒狀態(預設):主機工作空閒,可以接收資料;② 忙碌狀態:主機工作忙碌,暫時停止資料接收。 |
方法 operation:RecvSensorID | 接收感測器 ID 號 |
方法 operation:RecvSensorValue | 接收感測器採集的資料 |
方法 operation:SendSensorCMD | 傳送主機指令 |
方法 operation:StartMaster | 開始主機工作 |
方法 operation:StopMaster | 停止主機工作 |
方法 operation:RetMasterState | 返回主機狀態 |
工作流程簡單來說就是主機在空閒時,傳送資料查詢指令,接收感測器資料;在忙碌時,不接收資料。
我們來看如下示例程式碼,MasterClass 類具體方法實現先省略:
class MasterClass(SerialClass):
_# 類的初始化_
def __init__(self,state):
_# 呼叫父類的初始化方法,super() 函式將父類和子類連線_
super().__init__()
self.valuequeue = queue.Queue(5)
self.masterstatue = state
_# 開啟主機_
def StartMaster(self):
pass
_# 停止主機_
def StopMaster(self):
pass
_# 接收感測器ID號_
def RecvSensorID(self):
pass
_# 接收感測器資料_
def RecvSensorValue(self):
pass
_# 主機傳送命令_
def SendSensorCMD(self):
pass
_# 主機返回工作狀態_
def RetMasterStatue(self):
pass
這裡,我們使用佇列建立了一個感測器資料緩衝區:
self.valuequeue = queue.Queue(5)
我們使用 import queue 語句匯入佇列庫,佇列類似於一條管道,元素先進先出,元素進入使用 put()方法,元素獲取使用 get( )方法。關於新建的兩個類方法的具體實現可以看下一節內容。