python面向對象-封裝-property-介面-抽象-鴨子類型-03
- 2019 年 10 月 7 日
- 筆記
封裝
什麼是封裝
# 將複雜的醜陋的隱私的細節隱藏到內部,對外提供簡單的使用介面
或 # 對外隱藏內部實現細節,並提供訪問的介面
為什麼需要封裝
- 為了保證關鍵數據的安全性
- 對外部隱藏內部的實現細節,隔離複雜度
什麼時候需要封裝
# 當有一些數據不希望外界可以直接修改時
,# 當有一些函數不希望給外界使用時
如何使用封裝
語法:(給屬性或者方法前面加上 __ 雙下劃線,外界就訪問不到了)
用戶的身份證號等資訊屬於用戶的隱私,肯定不能直接暴露給外界可以直接訪問或修改,那麼就不能把它作為普通屬性了,應該是私有屬性
class Person: def __init__(self, id_number, name, age): # 身份證號碼肯定不能隨便改!那就需要給他隱藏起來(__屬性,隱藏起來) self.__id_number = id_number # ************ 把屬性隱藏起來 # self.id_number = id_number self.name = name self.age = age def show_id(self): print(self.__id_number) def __say_hi(self): print(f"hi, 我是{self.name}") p = Person('111111111111111111', 'jack', 29) p.id_number = '222' # 這裡其實是給對象加了個屬性 id_number(對象屬性的增刪改查) print(p.id_number) # 222 p.show_id() # 111111111111111111 # 並沒有受到影響 # p.__id_number # 報錯,pycharm沒有提示也找不到,AttributeError: 'Person' object has no attribute '__id_number' # p.__say_hi # 報錯,AttributeError: 'Person' object has no attribute '__say_hi'
封裝方法案例
用戶使用電腦只需要按下開機鍵即可,具體開機要涉及檢查硬體啊、載入內核、初始化內核等操作,用戶根本不需要去操作,也不需要去了解(不然還得學,那用個電腦這麼麻煩,估計是不用了),況且沒有接通電源是無法載入內核的(有先後順序),此時就可以把載入內核等方法封裝起來,作為私有方法,在暴露出來的介面中調用,這樣用戶只需要按下開機即可完成了。
class PC: def __init__(self, price, kind, color): self.price = price self.kind = kind self.color = color # 外部只需要調用這個open 就可以啟動電腦了,其他的歩鄹都不需要外界操作 def open(self): # 複雜的開機流程 print("接通電源") self.__check_device() print("載入內核") print("初始化內核") self.__start_services() print("啟動GUI") self.__login() # 必須先接通電源 @staticmethod # def check_device(): def __check_device(): print("硬體檢測1") print("硬體檢測2") print("硬體檢測3") print("硬體檢測4") # 必須先接通電源 @staticmethod # def start_services(): def __start_services(): print("啟動服務1") print("啟動服務2") print("啟動服務3") print("啟動服務4") # 必須先啟動了才能登錄 --> 不能讓外界直接調用登錄 @staticmethod # def login(): def __login(): print("login流程1.......") print("login流程2.......") print("login流程3.......") print("login流程4.......") pc = PC(5688, 'ASUS', 'black') pc.open() # 一鍵啟動 # 接通電源 # 硬體檢測1 # 硬體檢測2 # 硬體檢測3 # 硬體檢測4 # 載入內核 # 初始化內核 # 啟動服務1 # 啟動服務2 # 啟動服務3 # 啟動服務4 # 啟動GUI # login流程1....... # login流程2....... # login流程3....... # login流程4.......
被封裝內容的特點
- 外界不能直接訪問
- 類內部依然可以使用
許可權
利用好封裝的特性就可以控制屬性的許可權(接著往下看)
python中只有兩種許可權
- 1.公開的屬性或方法(默認就是公開的)
- 2.私有的屬性或方法,只能由當前類自己使用
在外界訪問私有內容
可以通過封裝非私有方法來實現(類內部還是可以訪問自身的私有屬性的)
''' 這是一個下載器類,需要提供一個快取大小這樣的屬性 快取大小不能超過記憶體限制 ''' class Downloader: def __init__(self, filename, url, buffer_size): self.filename = filename self.url = url # self.buffer_size = buffer_size self.__buffer_size = buffer_size # 一旦被私有後,外界就無法直接訪問了,應該給外界提供一個介面,可以改動 def start_download(self): # if self.buffer_size <= 1024*1024: if self.__buffer_size <= 1024*1024: print("開始下載...") else: print("記憶體超過限制!") # 可以在方法中添加一些額外的邏輯 def set_buffer_size(self, size): # 這裡可以加一些限制操作,限制大小或者登錄驗證,數據校驗 if not isinstance(size, int): print("緩衝區大小必須是整數!") return False self.__buffer_size = size def get_buffer_size(self): return self.__buffer_size d = Downloader("冰火兩重天", 'https://www.baidu.com', 1024*1024) # d.buffer_size = 1024*1024*1024 d.start_download() # 開始下載... d.set_buffer_size(1024 * 512) # 外界通過方法改動私有屬性 d.set_buffer_size('aa') # 外界通過方法改動私有屬性 # 緩衝區大小必須是整數! d.start_download() # 開始下載... print(d.get_buffer_size()) # 外界通過方法訪問私有屬性 # 524288 d.set_buffer_size(1024 * 1024 * 1024 / 2) # 這裡 / 2 變成了float 浮點型,類型不匹配了 # 緩衝區大小必須是整數! d.start_download() # 開始下載... # 這裡用的是之前的buffer_size,上面沒有改成功(不然超出大小了也下不了的) d.set_buffer_size(1024 * 1024 * 1024) # set_buffer_size() 里沒有做大小限制,所以其實是改成功了 d.start_download() # 超過大小限制,所以提示記憶體超過限制 # 記憶體超過限制!
好處:通過封裝的方法來修改、讀取、刪除(私有)屬性,可以在對屬性進行修改、讀取、刪除的時候可以做攔截,做一些邏輯操作
缺點:訪問的時候,訪問方式不統一,非私有變數直接 # 對象.屬性名
就可以訪問了,而私有變數因為用了方法封裝才能訪問,所以訪問的時候要調用方法才行
property 裝飾器
由來:通過方法來修改或訪問私有屬性,本身沒什麼問題,但他還是不怎麼好,這給對象的使用者帶來了麻煩,使用者必須知道哪些是普通屬性,哪些是私有屬性,需要使用不同的方式來調他們(獲取設置)。
而貼心的python提供了 property裝飾器
property 好處
# property 裝飾器可以解決上面的問題,把方法偽裝成屬性,讓私有屬性與普通屬性的調用方式一致
property 有三種裝飾器
''' @property(@property.getter): 用在獲取屬性的方法上(調用的時候名字應該和屬性一致) @key.setter:用在修改屬性的方法上(必須保持屬性名和property裝飾的函數的名字一致) @key.deleter:用在刪除屬性的方法上(必須保持屬性名和property裝飾的函數的名字一致) 注意:key是被property裝飾方法的名稱,也是屬性的名稱 其內部會創建一個對象,名稱就是函數名稱,所以在使用setter和deleter時,必須使用@對象的名稱 . 去調用方法,即 @對象.setter (這三個需要哪個就寫哪個) '''
案例
class A: def __init__(self, name, key): self.name = name self.__key = key def set_key(self, new_key): self.__key = new_key def get_key(self): return self.__key @property # 把一個方法偽裝成普通屬性,通過 . 來訪問調用 def key(self): # 可以改成其他名字,但調的時候也要改,通常情況下也是默認跟屬性名一致 # 邏輯處理 return self.__key @key.setter # 把一個私有的屬性通過方法偽裝成一個普通的屬性 def key(self, new_key): # 邏輯處理 self.__key = new_key @key.deleter # 在del 對象.key 的時候會執行這個 def key(self): # 判斷許可權再刪除 if '有許可權' == '有許可權': del self.key else: print(f"您沒有許可權刪除!") a = A('jack', 123) print(a.name) # jack print(a.get_key()) # 這樣需要記哪些屬性需要調方法,哪些直接就可以 . 訪問, 不太好 # 123 a.set_key(321) # 這樣也不太好 print(a.key) # 321 # 訪問與修改私有屬性 key (別說沒用,我這裡可以在裝飾的方法里寫一些邏輯操作,控制私有屬性(加許可權)) a.key = 987 print(a.key) # 987
python 實現封裝的原理
# 就是在載入類的時候,把 __ 替換成了 _類名__屬性(替換屬性名稱)
python一般不會強制要求程式設計師怎麼怎麼樣,比較靈活
通過property 實現計算屬性
計算屬性:屬性的值不能直接獲得,必須通過計算才能獲取
例如:正方形的面積屬性,是由邊長相乘得到的
class Square: # 正方形 def __init__(self, width): self.width = width self.area = self.width * self.width s = Square(10) print(s.area) # 100 s.width = 20 print(s.area) # 後續更改了width,它的值就不對了 # 100 class Square2: # 正方形 def __init__(self, width): self.width = width # self.area = self.width * self.width # 下面定義的時候要把這裡去掉 @property # 只要 . 這個屬性, 就會自動觸發這個函數 def area(self): return self.width * self.width s2 = Square2(10) print(s2.area) # 100 s2.width = 20 print(s2.area) # 400
小練習:計算BMI
# 練習: 定義一個類叫做person # 包含三個屬性 身高 體重 BMI # BMI的值需要通過計算得來 公式 體重 / 身高的平方 class Person: ''' height: 單位 m weight: 單位 kg ''' def __init__(self, height_meter, weight_kg): # 用變數名來提示調用者數據的單位 self.height = height_meter self.weight = weight_kg @property # 利用property 把方法偽裝成屬性 --> 計算屬性的原理 def bmi(self): return self.weight / pow(self.height, 2) p1 = Person(1.80, 65) print(p1.bmi) # 20.061728395061728
抽象類
抽象類:# 類中沒有方法的具體實現程式碼的類
介面
介面:# 一組功能的集合,但是介面中僅包含功能的名字,不包含具體實現程式碼。
生活中的案例:USB介面、HDMI、VGA、WLAN網線介面
介面本質:一套協議標準,遵循了這個標準的對象就能夠被調用(調誰都可以)
介面的目的:提高擴展性
例如:電腦提前制定一套USB介面協議,只要你的設備遵循了該協議,那麼它就可以被電腦使用,無所謂什麼類型(滑鼠、鍵盤…)
# 協議:支援打開關閉,讀寫數據 class USB: def open(self): pass def close(self): pass def read(self): pass def write(self): pass # 按USB標準製作滑鼠 class Mouse(USB): def open(self): # 打開方法 print("滑鼠開機了") def close(self): print("滑鼠關閉了") def read(self): print("獲取了游標位置") def write(self): # 請忽略滑鼠配置 print("滑鼠可以寫入燈光顏色等數據...") # 至此,Mouse就算是一個合格的USB設備了 # 按USB標準製作鍵盤 class KeyBoard(USB): def open(self): # 打開方法 print("鍵盤開機了") def close(self): print("鍵盤關閉了") def read(self): print("獲取了按鍵字元...") def write(self): # 請忽略滑鼠配置 print("鍵盤可以寫入燈光顏色等數據...") # 至此,Mouse就算是一個合格的USB設備了 # ..........其他符合USB介面協議的設備........... def pc(usb_device): usb_device.open() usb_device.read() usb_device.write() usb_device.close() mouse = Mouse() # 將滑鼠傳給pc pc(mouse) # 滑鼠開機了 # 獲取了游標位置 # 滑鼠不支援寫入數據 # 滑鼠關閉了 key_board = KeyBoard() pc(key_board) # 鍵盤開機了 # 獲取了按鍵字元... # 鍵盤可以寫入燈光顏色等數據... # 鍵盤關閉了 # 上述過程,滑鼠鍵盤的使用都沒有改變pc 的程式碼(使用方式),體現了擴展性和復用性
小結
在上述案例中,pc的程式碼一旦完成,後期無論什麼樣的設備,只要遵循了USB介面協議,就都能夠被pc識別並調用。 介面主要是為了方便對象的使用者,降低使用者的學習難度,只需要學習一套使用方法就可以以不變應萬變了。
如果不按標準來:如果子類沒有按照你的協議來設計,你也沒辦法限制他,這將導致程式碼無法運行
那麼下面的abc模組了解一下。
abc模組
abc模組的abc: # abc是 abstract class(抽象類) 的縮寫,不是隨便寫的
作用:可以限制子類必須實現類中定義的抽象方法(@abc.abstractmethod)(防止一些類不按規定來寫)
import abc # abc是 abstract class(抽象類) 的縮寫,不是隨便寫的 class AClass(metaclass=abc.ABCMeta): # 抽象類 @abc.abstractmethod # 裝飾抽象方法 def run(self): pass @abc.abstractmethod # 裝飾抽象方法 def run2(self): pass class B(AClass): pass # b = B() # 直接報錯,TypeError: Can't instantiate abstract class B with abstract methods run class C(AClass): def run(self): print("runrunrun....") # c = C() # 少實現了一個方法,直接報錯 TypeError: Can't instantiate abstract class C with abstract methods run2 class D(AClass): def run(self): print("runrunrun....") def run2(self): print("runrunrun2....") d = D() # 把抽象類的方法都實現了,不會報錯
鴨子類型
由來:python 一般不會限制你必須怎麼寫,作為一個優秀的程式設計師,就應該自覺遵守相關協議,所以就有了鴨子類型這一說
如果這個對象長得像鴨子(屬性),走路像鴨子(方法),那麼他就是鴨子(沒有說必須方方面面都像)
鴨子類型:擁有相同屬性和方法,那麼就可以把它看成同樣的類,也可以提高擴展性
程式碼案例
# 默認按USB標準製作滑鼠 class Mouse: @staticmethod def open(): # 打開方法 print("滑鼠開機了") @staticmethod def close(): print("滑鼠關閉了") @staticmethod def read(): print("獲取了游標位置") @staticmethod def write(): # 請忽略滑鼠配置 print("滑鼠可以寫入燈光顏色等數據...") # 默認按USB標準製作鍵盤 class KeyBoard: @staticmethod def open(): # 打開方法 print("鍵盤開機了") @staticmethod def close(): print("鍵盤關閉了") @staticmethod def read(): print("獲取了按鍵字元...") @staticmethod def write(): # 請忽略滑鼠配置 print("鍵盤可以寫入燈光顏色等數據...") # ..........其他符合USB介面協議的設備........... def pc(usb_device): usb_device.open() usb_device.read() usb_device.write() usb_device.close() mouse = Mouse() # 將滑鼠傳給pc pc(mouse) # 可以正常使用 # 滑鼠開機了 # 獲取了游標位置 # 滑鼠不支援寫入數據 # 滑鼠關閉了 key_board = KeyBoard() pc(key_board) # 可以正常使用 # 鍵盤開機了 # 獲取了按鍵字元... # 鍵盤可以寫入燈光顏色等數據... # 鍵盤關閉了
上面的案例中依然沒有改變pc中的程式碼,而Mouse、KeyBoard這樣的類也可以直接交給pc使用,他們看起來用起來都像是前面寫的USB介面協議,所以他們就是鴨子類型
介面與抽象類小結:
小結: 介面是一套協議規範,明確子類們應該具備哪些功能 抽象類是用於強制要求子類必須按照協議中的規定來(介面中定義的)實現 然而python 不推崇限制你的語法,我們可以設計成鴨子類型,既讓多個不同類對象具備相同的屬性和方法,對於使用者而言,就可以以不變應萬變,輕鬆地使用各種符合協議的對象
tips: markdown寫部落格還是挺爽的,真香~
附上使用Typora軟體來寫部落格的快捷鍵參考部落格,一起用起來吧~
markdown文件可以保存在本地,用編輯器打開即可看到效果,簡單輕快,很多部落格平台都是支援markdown格式的,所以其擴展性會很高(既可以本地化,又可以擁有擴展性,果斷學起來,體驗兩天,比用部落格園那個默認的編輯器給力呀,上手了寫起來更快~)
在剛開始用的時候可以開個快捷鍵的網頁在旁邊要用什麼了就看一眼快捷鍵,直接使用,多用幾次就記住了。