本文我們主要介紹了數據封裝的基本概念和特性,如何設置自定義類的私有屬性和私有方法,protect屬性的概念和特點。 ...
全網最適合入門的面向對象編程教程:06 類和對象的 Python 實現-自定義類的數據封裝
摘要:
本文我們主要介紹了數據封裝的基本概念和特性,如何設置自定義類的私有屬性和私有方法,protect 屬性的概念和特點。
往期推薦:
全網最適合入門的面向對象編程教程:00 面向對象設計方法導論
全網最適合入門的面向對象編程教程:01 面向對象編程的基本概念
全網最適合入門的面向對象編程教程:02 類和對象的 Python 實現-使用 Python 創建類
全網最適合入門的面向對象編程教程:03 類和對象的 Python 實現-為自定義類添加屬性
全網最適合入門的面向對象編程教程:04 類和對象的Python實現-為自定義類添加方法
全網最適合入門的面向對象編程教程:05 類和對象的Python實現-PyCharm代碼標簽
更多精彩內容可看:
給你的 Python 加加速:一文速通 Python 並行計算
一個MicroPython的開源項目集錦:awesome-micropython,包含各個方面的Micropython工具庫
文檔和代碼獲取
可訪問如下鏈接進行對文檔下載:
本文檔主要介紹如何使用 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 強調封裝性,鼓勵使用公共方法來訪問和修改屬性,而不是直接在外部訪問。這種做法有助於提高代碼的可維護性,防止意外的修改和增加代碼的靈活性。