Python 描述符簡介

發表於2016-11-29

簡介

Python 2.2 引進了 Python 描述符,同時還引進了一些新的樣式類,但是它們並沒有得到廣泛使用。Python 描述符是一種建立託管屬性的方法。除了其他優點外,託管屬性還用於保護屬性不受修改,或自動更新某個依賴屬性的值。

描述符增加了對 Python 的理解,改善了編碼技能。本文介紹了描述符協議,並演示瞭如何建立和使用描述符。

描述符協議

Python 描述符協議 只是一種在模型中引用屬性時指定將要發生事件的方法。它允許程式設計人員輕鬆、有效地管理屬性訪問:

  • set
  • get
  • delete

在其他程式語言中,描述符被稱作 settergetter,而公共函式用於獲得 (Get) 和設定 (Set) 一個私有變數。Python 沒有私有變數的概念,而描述符協議可以作為一種 Python 的方式來實現與私有變數類似的功能。

總的來說,描述符就是一個具有繫結行為的物件屬性,其屬性訪問將由描述符協議中的方法覆蓋。這些方法為 __get____set____delete__。如果這些方法中的任何一個針對某個物件定義,那麼它就被認為是一個描述符。通過 清單 1 進一步瞭解這些方法。

清單 1. 描述符方法

其中:

__get__ 用於訪問屬性。它返回屬性的值,或者在所請求的屬性不存在的情況下出現 AttributeError 異常。

__set__ 將在屬性分配操作中呼叫。不會返回任何內容。

__delete__ 控制刪除操作。不會返回內容。

需要注意,描述符被分配給一個類,而不是例項。修改此類,會覆蓋或刪除描述符本身,而不是觸發它的程式碼。

需要使用描述符的情況

考慮 email 屬性。在向該屬性分配值之前,需要對郵件格式進行檢驗。該描述符允許通過一個正規表示式處理電子郵件,然後對格式進行檢驗後將它分配給一個屬性。

在其他許多情況下,Python 協議描述符控制對屬性的訪問,如保護 name 屬性。

建立描述符

您可以通過許多方式建立描述符:

  • 建立一個類並覆蓋任意一個描述符方法:__set____ get____delete__。當需要某個描述符跨多個不同的類和屬性,例如型別驗證,則使用該方法。
  • 使用屬性型別,這種方法可以更加簡單、靈活地建立描述符。
  • 使用屬性描述符,它結合了屬性型別方法和 Python 描述符。

以下示例在其操作方面均相似。不同之處在於實現方法。

使用類方法建立描述符

清單 2 演示在 Python 中控制屬性分配非常簡單。

清單 2. 使用類方法建立描述符

使用這些程式碼並檢視輸出:

通過以下方法覆蓋父類的 __set__()__get__()__delete__() 方法,建立一個描述符類:

  • get 將輸出 Getting
  • delete 將輸出 Deleting
  • set 將輸出 Setting

並在分配之前將屬性值修改為標題(第一個字母大寫,其他字母為小寫)。這樣做有助於儲存和輸出名稱。

大寫轉換同樣可以移動到 __get__() 方法。_value 有一個初始值,並根據 get 請求轉換為標題。

使用屬性型別建立描述符

雖然 清單 2 中定義的描述符是有效的且可以正常使用,但是還可以使用屬性型別的方法。通過使用 property(),可以輕鬆地為任意屬性建立可用的描述符。建立 property() 的語法是 property(fget=None, fset=None, fdel=None, doc=None),其中:

  • fget:屬性獲取方法
  • fset:屬性設定方法
  • fdel:屬性刪除方法
  • doc:docstring

使用屬性重寫該例子,如 清單 3 所示。

清單 3. 使用屬性型別建立描述符

使用該程式碼並檢視輸出:

顯然,結果是相同的。注意,fgetfsetfdel 方法是可選的,但是如果沒有指定這些方法,那麼將在嘗試各個操作時出現一個 AttributeError 異常。例如,宣告 name 屬性時,fset 被設定為 None,然後開發人員嘗試向 name 屬性分配值。這時將出現一個 AttributeError 異常。

這種方法可以用於定義系統中的只讀屬性。

輸出為:

使用屬性修飾符建立描述符

可以使用 Python 修飾符建立描述符,如 清單 4 所示。Python 修飾符是對 Python 語法的特定修改,能夠更方便地更改函式和方法。在本例中,將修改屬性管理方法。在 developerWorks 文章 Decorators make magic easy 中尋找更多有關應用 Python 修飾符的資訊。

清單 4. 使用屬性修飾符建立描述符

在執行時建立描述符

前面的所有例子都使用了 name 屬性。該方法的侷限性在於需要對各個屬性分別覆蓋 __set__()__get__()__delete__()清單 5 提供了一個可能的解決方案,幫助開發人員在執行時新增 property 屬性。該解決方案使用屬性型別構建資料描述符。

清單 5. 在執行時建立描述符

讓我們執行這段程式碼:

這將在執行時建立 namephone 屬性。它們可以根據相應的名稱進行訪問,但是按照 _setProperty 方法中的定義,將在物件名稱空間目錄中儲存為 _name 和 _phone。基本上,namephone 是對內部的 _name 和 _phone 屬性的訪問符。

當開發人員嘗試新增 name property 屬性時,您可能對系統中的 _name 屬性存在疑問。實際上,它將用新的 property 屬性覆蓋現有的 _name 屬性。這些程式碼允許控制如何在類內部處理屬性。

結束語

Python 描述符可以利用新的樣式類實現強大而靈活的屬性管理。通過結合使用描述符,可以實現優雅的程式設計,允許建立 SettersGetters 以及只讀屬性。它還允許您根據值或型別請求進行屬性驗證。您可以在許多不同的領域應用描述符,但是使用時需保持謹慎的態度,避免由於覆蓋普通物件行為而產生不必要的程式碼複雜性。

相關文章