淺談對屬性描述符__get__、__set__、__delete__的理解

来源:https://www.cnblogs.com/huageyiyangdewo/archive/2023/04/12/17311733.html
-Advertisement-
Play Games

本文已經收錄到Github倉庫,該倉庫包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等核心知識點,歡迎star~ Github地址 如果訪問不了Github,可以訪 ...


1、屬性描述符的基礎介紹

1.1 何為屬性描述符?

屬性描述符是一種Python語言中的特殊對象,用於定義和控制類屬性的行為。屬性描述符可以通過定義__get__、__set__、__delete__方法來控制屬性的讀取、賦值和刪除操作。

通過使用屬性描述符,可以實現對屬性的訪問控制、類型檢查、計算屬性等高級功能。

如果一個對象定義了這些方法中的任何一個,它就是一個描述符。

看完上面的文字描述,是不是感覺一頭霧水,沒關係,接下來通過一個簡單的案例來講解屬性描述符的作用。

1.2 為什麼需要屬性描述符?

假設我們現在要做一個成績管理系統,在定義學生類時,我們可能這樣寫:

class Student(object):

    def __init__(self, name, age, cn_score, en_score):
        self.name = name
        self.age = age
        self.cn_score = cn_score
        self.en_score = en_score

    def __str__(self):
        return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)


xiaoming = Student("xiaoming", 18, 70, 55)
print(xiaoming)
1.2.1 init函數中做參數校驗

因為python是動態語言類型,不像靜態語言那樣,可以給參數指定類型,所以在傳參時,無法得知參數是否正確。比如,當cn_score傳入的值為字元串時,程式並不會報錯。這個時候,一般就會想到對傳入的參數做校驗,當傳入的參數不符合要求時,拋錯。

class Student(object):

    def __init__(self, name, age, cn_score, en_score):
        self.name = name
        if not isinstance(age, int):
            raise TypeError("age must be int")
        if age <= 0:
            raise ValueError("age must be greater than 0")
        self.age = age

        if not isinstance(cn_score, int):
            raise TypeError("cn_score must be int")
        if 0 <= cn_score <= 100:
            raise ValueError("cn_score must be between 0 and 100")
        self.cn_score = cn_score

        if not isinstance(en_score, int):
            raise TypeError("en_score must be int")
        if 0 <= en_score <= 100:
            raise ValueError("en_score must be between 0 and 100")
        self.en_score = en_score

    def __str__(self):
        return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)


xiaoming = Student("xiaoming", -1, 70, 55)
print(xiaoming)

雖然上面的代碼可以實現參數校驗,但是過多的邏輯判斷在初始化函數裡面,會導致函數特別臃腫,當增加新的參數時,需要增加邏輯判斷,一方面重覆代碼增加,另外也不符合開閉原則

1.2.2 使用property做參數校驗

這個時候該怎麼處理呢,我們知道python的內置函數 property可用於裝飾方法,使方法之看起來像屬性一樣。我們可以藉助此函數來優化代碼,優化後如下:

class Student(object):

    def __init__(self, name, age, cn_score, en_score):
        self.name = name
        self.age = age
        self.cn_score = cn_score
        self.en_score = en_score

    @property
    def age(self):
        return self.age

    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise TypeError("age must be int")
        if value <= 0:
            raise ValueError("age must be greater than 0")
        self.age = value

    @property
    def cn_score(self):
        return self.cn_score

    @cn_score.setter
    def cn_score(self, value):
        if not isinstance(value, int):
            raise TypeError("cn_score must be int")
        if 0 <= value <= 100:
            raise ValueError("cn_score must be between 0 and 100")
        self.cn_score = value

    @property
    def en_score(self):
        return self.en_score

    @en_score.setter
    def en_score(self, value):
        if not isinstance(value, int):
            raise TypeError("en_score must be int")
        if 0 <= value <= 100:
            raise ValueError("en_score must be between 0 and 100")
        self.en_score = value

    def __str__(self):
        return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)


xiaoming = Student("xiaoming", -1, 70, 55)
print(xiaoming)

現在代碼看起來已經挺不錯的了,確實。但是想想平常開發中,我們使用Diango 的 ORM 時,定義model時,只需要定義 modle 的屬性,就可以使其完成參數的校驗,比如ip = models.CharField(max_length=20, db_index=True, verbose_name='IP')。這是怎麼做到的呢?

1.2.3 使用屬性描述符做參數校驗

其實,Django 是使用到了Python的屬性描述符 __get__、__set__。接下來,我們使用上面的兩個方法,來進行改造。代碼如下:

class Score:
    def __init__(self, score):
        self.score = score

    def __get__(self, instance, owner):
        return self.score

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError("value must be int")
        if 0 <= value <= 100:
            self.score = value
        else:
            raise ValueError("value must be between 0 and 100")


class Age:

    def __init__(self, age):
        self.age = age

    def __get__(self, instance, owner):
        return self.age

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError("age must be int")
        if value <= 0:
            raise ValueError("age must be greater than 0")
        self.age = value


class Student(object):

    age = Age(0)
    cn_score = Score(0)
    en_score = Score(0)

    def __init__(self, name, _age, _cn_score, _en_score):
        self.name = name
        # 通過這裡參數名稱的區別,我們可以更加明確的知道,是調用
        self.age = _age
        self.cn_score = _cn_score
        self.en_score = _en_score

    def __str__(self):
        return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)


xiaoming = Student("xiaoming", -1, 70, 55)
print(xiaoming)

通過上面的定義,也能夠實現之前的功能,而且代碼重用度更高,看起來也更加簡潔。

1.3 屬性描述符分類

常見的屬性描述符包括數據描述符和非數據描述符。

  • 數據描述符

是指同時定義了__get__、__set__方法的屬性描述符,它可以完全控制屬性的讀寫操作。

  • 非數據描述符

是指只定義了__get__方法的屬性描述符,它只能控制屬性的讀取操作,而不能控制屬性的賦值和刪除操作。

2、屬性描述符的詳細介紹

2.1 屬性描述符的調用時機

描述符本質就是一個新式類,在這個新式類中,至少實現了__get__、__set__、__delete__中的一個,這也被稱為描述符協議。

  • __get__():調用一個屬性時,觸發

  • __set__():為一個屬性賦值時,觸發

  • __delete__():採用del刪除屬性時,觸發

通過下麵的例子將更加清晰的知道 屬性描述符的調用時機。

class Age:

    def __init__(self, age):
        self.age = age

    def __get__(self, instance, owner):
        print("coming __get__")
        return self.age

    def __set__(self, instance, value):
        print("coming __set__")
        if not isinstance(value, int):
            raise TypeError("age must be int")
        if value <= 0:
            raise ValueError("age must be greater than 0")
        self.age = value

    def __delete__(self, instance):
        print("coming __del__")
        del self.age


class Student(object):

    age = Age(0)

    def __init__(self, name):
        self.name = name


xiaoming = Student("xiaoming")
xiaoming.age = 9
print(xiaoming.age)
del xiaoming.age


#################
結果:
coming __set__
coming __get__
coming __del__

2.2 屬性的搜索順序

這裡跟屬性描述符關係不是特別大,主要是看看屬性的搜索順序。

預設的屬性訪問是從對象的字典中 get, set, 或者 delete 屬性。例如a.x的查找順序是:

a.__getattribute__() -> a.__dict__['age'] -> type(a).__dict__['age'] -> type(a)的基類(不包括元類)-> a.__getattr__ -> 拋錯

如果查找的值是對象定義的描述方法之一,python可能會調用描述符方法來重載預設行為,發生在這個查找環節的哪裡取決於定義了哪些描述符方法。

1、非數據描述器,實例的屬性搜索順序如下:

a.__getattribute__() -> a.__dict__['age'] -> a.__get__() -> type(a).__dict__['age'] -> type(a)的基類(不包括元類)-> a.__getattr__ -> 拋錯

class Age(object):
    def __get__(self, instance, owner):
        print("coming __get__")
        return "__get__"

    # def __set__(self, instance, value):
    #     print("coming __set__")
    #     self.age = value


class A2(object):

    age = 10
    def __init__(self):

        self.age = 1000


class A(object):

    age = Age()

    def __init__(self):
        super().__init__()

    # def __getattribute__(self, item):
    #     print("coming __getattribute__")
    #     return "xxx"
    #
    def __getattr__(self, item):
        print("coming __getattr__")
        return "__getattr__"


a = A()
print(a.age)

2、數據描述器,實例的屬性搜索順序如下:

a.__getattribute__() -> a.__get__() -> a.__dict__['age'] -> type(a).__dict__['age'] -> type(a)的基類(不包括元類)-> a.__getattr__ -> 拋錯

class Age(object):
    def __get__(self, instance, owner):
        print("coming __get__")
        return "__get__"

    def __set__(self, instance, value):
        print("coming __set__")
        self.age = value


class A2(object):


    def __init__(self):

        self.age = 1000


class A(object):

    age = Age()

    def __init__(self):
        self.age = 100
        super().__init__()

    # def __getattribute__(self, item):
    #     print("coming __getattribute__")
    #     return "xxx"
    #
    def __getattr__(self, item):
        print("coming __getattr__")
        return "__getattr__"


a = A()
print(a.age)

參考鏈接:

【案例講解】Python為什麼要使用描述符?

[屬性描述符:__get__函數、__set__函數和__delete_函數](


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 讓對象保持消息靈通 #01需求 一個WeatherData對象負責追蹤目前的天氣狀況(溫度,濕度,氣壓)。希望你們能建立一個應用,有三種佈告板,分別顯示目前的狀況、氣象統計及簡單的預報。當WeatherObject對象獲得最新的測量數據時,三種佈告板必須實時更新。而且,這是一個可以擴展的氣象站,We ...
  • 簡介 解釋器模式(Interpreter Pattern)是一種行為型設計模式。這種模式實現了一個表達式介面,該介面解釋一個特定的上下文。這種模式常被用在 SQL 解析、符號處理引擎等。 解釋器模式常用於對簡單語言的編譯或分析實例中,為了掌握好它的結構與實現,必須先瞭解編譯原理中的“文法、句子、語法 ...
  • 本文探討了 API 管理在數字化轉型中的重要性,以及 API 管理面臨的挑戰和發展機遇。文章重點介紹了十大 API 管理髮展趨勢,包括 API 安全性、API 標準化、雲端 API 管理解決方案、低代碼 API 平臺、API 市場、新興 API 協議、人工智慧與 API、開發者體驗、API 分析和無 ...
  • 軟體開發: 唯一不變的是變化: 不管設計的多好,隨著時間推移,應用必定成長和變更 設計原則: 封裝變化:設別應用中變化的方面,把它們和不變的方面分開; (把會變化的部分取出並封裝,這樣,就可以修改或者擴展這個部分,而不會影響其他不需要變化的部分) 針對介面編程,而不是針對實現編程(介面,實際上就是針 ...
  • 久違的PAT,由於考研408數據結構中有一定需要,同時也是對先前所遺留的競賽遺憾進行一定彌補 ,再次繼續PAT甲級1003.。 As an emergency rescue team leader of a city, you are given a special map of your coun ...
  • 如果想增強一個方法的功能,無非就是直接在方法體內直接修改。但這也無非給一些有代碼潔癖人士一絲絲不悅!於是乎我們即不想在原來的代碼里修改,又不想把原有的代碼重新寫一次,那麼前輩們就發明瞭代理. 註意:本文以 JdkProxy 為基礎展開所有描述! 參與對象 那麼一個代理過程參與的對象有以下幾項: 目標 ...
  • 官方文檔:https://www.xuxueli.com/xxl-job/ XXL-JOB 是一個分散式任務調度平臺,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展。現已開放源代碼並接入多家公司線上產品線,開箱即用。 說明:官方文檔很重要,而且非常詳細,要慢慢適應看官方文檔進行學習! PS:下麵 ...
  • demo軟體園每日更新資源,請看到最後就能獲取你想要的: 1.歡樂商城源碼/品雲購商城源碼/英文版商城源碼/全開源 可二開 商城源碼/英文版商城源碼/全開源 可二開 出海項目源碼 後臺為中文語言 頁面效果: 2.SQL學習指南(第2版) 這是一本關於SQL的書,不是關於資料庫的。以MySQL為例來講 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...