1. 程式人生 > >python面向物件-封裝-property-介面-抽象-鴨子型別-03

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 不推崇限制你的語法,我們可以設計成鴨子型別,既讓多個不同類物件具備相同的屬性和方法,對於使用者而言,就可以以不變應萬變,輕鬆地使用各種符合協議的物件
'''