全網最適合入門的物件導向程式設計教程:06 類和物件的 Python 實現-自定義類的資料封裝
摘要:
本文我們主要介紹了資料封裝的基本概念和特性,如何設定自定義類的私有屬性和私有方法,protect 屬性的概念和特點。
往期推薦:
學嵌入式的你,還不會物件導向??!
全網最適合入門的物件導向程式設計教程:00 物件導向設計方法導論
全網最適合入門的物件導向程式設計教程:01 物件導向程式設計的基本概念
全網最適合入門的物件導向程式設計教程:02 類和物件的 Python 實現-使用 Python 建立類
全網最適合入門的物件導向程式設計教程:03 類和物件的 Python 實現-為自定義類新增屬性
全網最適合入門的物件導向程式設計教程:04 類和物件的Python實現-為自定義類新增方法
全網最適合入門的物件導向程式設計教程:05 類和物件的Python實現-PyCharm程式碼標籤
更多精彩內容可看:
給你的 Python 加加速:一文速通 Python 平行計算
一文搞懂 CM3 微控制器除錯原理
肝了半個月,嵌入式技術棧大彙總出爐
電子計算機類比賽的“武林秘籍”
一個MicroPython的開源專案集錦:awesome-micropython,包含各個方面的Micropython工具庫
文件和程式碼獲取
可訪問如下連結進行對文件下載:
FreakStudio-Python物件導向文件
本文件主要介紹如何使用 Python 進行物件導向程式設計,需要讀者對 Python 語法和微控制器開發具有基本瞭解。相比其他講解 Python 物件導向程式設計的部落格或書籍而言,本文件更加詳細、側重於嵌入式上位機應用,以上位機和下位機的常見串列埠資料收發、資料處理、動態圖繪製等為應用例項,同時使用 Sourcetrail 程式碼軟體對程式碼進行視覺化閱讀便於讀者理解。
相關示例程式碼獲取連結如下:
FreakStudio-Python物件導向示例程式碼
正文
物件導向程式設計的一個重要特點就是資料封裝。在上面的 SerialClass 類中,有關於串列埠波特率、裝置名稱等多個屬性,我們編寫程式碼的過程中,往往不希望別的模組可以直接訪問本模組的資料,而僅透過呼叫我們的介面函式來訪問資料,也就是高內聚和低耦合原則。
在 Python 中,我們可以透過給屬性或方法命名時可以用兩個下劃線作為開頭來改變屬性和方法的訪問許可權。在 Python 中,屬性和方法的訪問許可權只有兩種,也就是公開的和私有的,對於私有屬性或私有方法來說,是不允許外界訪問的。
- (1)類的私有屬性格式:__private_attrs 兩個下劃線開頭,宣告該屬性為私有,不能在類的外部被使用或直接訪問。在類內部的方法中使用時__private_attrs。
- (2)類的私有方法格式:__private_method:兩個下劃線開頭,宣告該方法為私有方法,不能在類的外部呼叫。在類的內部呼叫 self.__private_methods。
這裡,我們聯絡上一節中串列埠收發的相關方法,可以看到在使用串列埠收發的相關方法時,我們並沒有判斷當前串列埠的狀態,如果串列埠狀態為關閉,那麼就不能正常工作。於是我們新增了一個__devstate 私有屬性用於標識串列埠狀態,同時新增了 RetSerialState()方法用於外界獲取串列埠狀態,示例程式碼如下:
import serial
import serial.tools.list_ports
class SerialClass:
_# 初始化_
_# 使用預設引數_
def __init__(self,
devport = "COM17",
devbaudrate = 115200,
devbytesize = serial.EIGHTBITS,
devparity = serial.PARITY_NONE,
devstopbits = serial.STOPBITS_ONE):
_# 直接傳入serial.Serial()類_
self.dev = serial.Serial()
self.dev.port = devport
self.dev.baudrate = devbaudrate
self.dev.bytesize = devbytesize
self.dev.parity = devparity
self.dev.stopbits = devstopbits
_# 表示串列埠裝置的狀態-開啟或者關閉_
_# 初始化時為關閉_
self.__devstate = False
_# 開啟串列埠_
def OpenSerial(self):
self.dev.open()
self.__devstate = True
_# 關閉串列埠_
def CloseSerial(self):
self.dev.close()
self.__devstate = False
_# 串列埠讀取_
def ReadSerial(self):
if self.__devstate:
_# 非阻塞方式讀取_
_# 按行讀取_
data = self.dev.readline()
_# 收到為二進位制資料_
_# 用utf-8編碼將二進位制資料解碼為unicode字串_
_# 字串轉為int型別_
data = int(data.decode('utf-8', 'replace'))
return data
_# 串列埠寫入_
def WriteSerial(self,write_data):
if self.__devstate:
_# 非阻塞方式寫入_
self.dev.write(write_data.encode())
_# 輸出換行符_
_# write的輸入引數必須是bytes 格式_
_# 字串資料需要encode()函式將其編碼為二進位制資料,然後才可以順利傳送_
_# \r\n表示換行回車_
self.dev.write('\r\n'.encode())
def RetSerialState(self):
if self.dev.isOpen():
self.__devstate = True
return True
else:
self.__devstate = False
return False
_# 生成串列埠類的例項_
serdev = SerialClass()
print("serdev state :",serdev.RetSerialState())
serdev.OpenSerial()
print("serdev state :",serdev.RetSerialState())
serdev.CloseSerial()
print("serdev state :",serdev.RetSerialState())
print(serdev.__devstate)
這裡我們在初始化方法中使用了預設引數,即在定義方法時,直接給形式引數指定一個預設值,這樣的話,即便初始化時沒有給擁有預設值的形參傳遞引數,該引數可以直接使用定義函式時設定的預設值。
程式碼執行結果如下,可以看到在開啟或關閉串列埠裝置時,RetSerialState()方法可以正常獲取串列埠狀態,而當我們訪問類內私有屬性時,程式報錯:
好的,現在我們終於鬆了一口氣,宣告__devstate 為私有屬性後,外部終於無法訪問我們的資料了。但是真的是這樣嗎?
嘗試將:
print(serdev.__devstate)
修改為:
print(serdev._SerialClass__devstate)
此時,我們執行程式,發現程式碼輸出如下:
壞了,怎麼外界還能訪問我們類內私有屬性?這是什麼魔法?
這是 Python 的名字改裝在起作用。Python 不允許例項化的類訪問私有資料,但你可以使用 object._className__attrName( 物件名._類名__私有屬性名 )訪問屬性。
由此可知,在 Python 中私有屬性為假私有屬性。那為什麼不從語法上保證 private 欄位的私密性呢?用最簡單的一句話來說:We are all consenting adults here(我們都是成年人)。正如 Python 程式設計師的觀點:開放要比封閉好。
透過以上講解,我們實際上已經對類的封裝特性有所瞭解,所謂封裝就是:
"隱藏一切可以隱藏的實現細節,只向外界暴露(提供)簡單的程式設計介面"。我們在類中定義的方法其實就是把資料和對資料的操作封裝起來了,在我們建立了物件之後,只需要給物件傳送一個訊息(呼叫方法)就可以執行方法中的程式碼,也就是說我們只需要知道方法的名字和傳入的引數(方法的外部檢視),而不需要知道方法內部的實現細節(方法的內部檢視)。
對於類的私有屬性來說,子類是無法訪問的,私有變數只有本類的內部能直接呼叫。這時我們可以用 protect 屬性進行修改,在屬性和方法前加一個下劃線就是 protect 型別了。類的 protect 屬性,子類可以繼承,同時例項物件、類物件都能直接呼叫 protect 屬性、方法。
受保護屬性在類的外部是可見但不建議直接訪問,其命名約定表示它們是內部實現的一部分,不應該被直接訪問。總體而言,Python 強調封裝性,鼓勵使用公共方法來訪問和修改屬性,而不是直接在外部訪問。這種做法有助於提高程式碼的可維護性,防止意外的修改和增加程式碼的靈活性。