python面向物件-封裝-property-介面-抽象-鴨子型別-03
封裝
什麼是封裝:
# 將複雜的醜陋的隱私的細節隱藏到內部,對外提供簡單的使用介面
或 # 對外隱藏內部實現細節,並提供訪問的介面
為什麼需要封裝
- 1.為了保證關鍵資料的安全性
- 2.對外部隱藏內部的實現細節,隔離複雜度
什麼時候需要封裝:
# 當有一些資料不希望外界可以直接修改時
,# 當有一些函式不希望給外界使用時
如何使用封裝
語法:(給屬性或者方法前面加上 __ 雙下劃線,外界就訪問不到了)
使用者的身份證號等資訊屬於使用者的隱私,肯定不能直接暴露給外界可以直接訪問或修改,那麼就不能把它作為普通屬性了,應該是私有屬性
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中只有兩種許可權
- 公開的(預設就是公開的)
- 私有的,只能由當前類自己使用
在外界訪問私有內容
可以通過封裝非私有方法來實現(類內部還是可以訪問自身的私有屬性的)
''' 這是一個下載器類,需要提供一個快取大小這樣的屬性 快取大小不能超過記憶體限制 ''' 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.setter): 用在獲取屬性的方法上(呼叫的時候名字應該和屬性一致)
@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的值需要通過計算得來 公式 體重 / 身高的平方
介面
介面:# 一組功能的集合,但是介面中僅包含功能的名字,不包含具體實現程式碼。
生活中的案例: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 一般不會限制你必須怎麼寫,作為一個優秀的程式設計師,就應該自覺遵守相關協議,所以就有了鴨子型別這一說
如果這個物件長得像鴨子(屬性),走路像鴨子(方法),那麼他就是鴨子(沒有說必須方方面面都像)
鴨子型別:擁有相同屬性和方法,那麼就可以把它看成同樣的類,也可以提高擴充套件性
程式碼案例(去掉介面,自覺遵守,不改pc程式碼)
介面與抽象類小結:
'''
介面是一套協議規範,明確子類們應該具備哪些功能
抽象類是用於強制要求子類必須按照協議中的規定來(介面中定義的)實現
然而python 不推崇限制你的語法,我們可以設計成鴨子型別,既讓多個不同類物件具備相同的屬性和方法,對於使用者而言,就可以以不變應萬變,輕鬆地使用各種符合協議的物件
'''