Python中描述符protocol 描述符協議__get__和__set__實現原理

描述符是python中用于自定義屬性訪問行為的對象,其核心在于實現__get__、__set__和__delete__方法。1. __get__用于獲取屬性值,當訪問屬性時觸發;2. __set__用于設置屬性值,控制賦值過程;3. __delete__用于刪除屬性。描述符必須作為類屬性存在才生效,且數據描述符(含__set__或__delete__)優先級高于實例字典。若將值存儲在描述符自身,會導致所有實例共享該值,應避免此做法。掌握描述符機制有助于深入理解屬性訪問、裝飾器及Property等內置工具的實現原理,并可用于高級特性如類型檢查和自動屬性管理。

python中,描述符(Descriptor)是一種實現特定協議的對象,這個協議包含 __get__、__set__ 和 __delete__ 方法。通過這些方法,我們可以自定義類屬性的訪問行為。理解描述符的工作機制,有助于更深入掌握屬性訪問、裝飾器、以及像 property 這樣的內置工具背后的原理。

描述符協議的基本結構

描述符的核心在于它定義了如何通過類或實例訪問屬性。一個對象只要實現了以下任意一個方法,就可以被稱為描述符:

  • __get__(self, instance, owner):用于獲取屬性值。
  • __set__(self, instance, value):用于設置屬性值。
  • __delete__(self, instance):用于刪除屬性。

這些方法中的參數含義如下:

  • self:描述符實例本身。
  • instance:使用該描述符的類實例(如果是通過實例訪問的話)。
  • owner:使用該描述符的類(即實例所屬的類)。

比如,當你訪問 obj.attr 時,如果 attr 是一個描述符對象,那么實際上會調用它的 __get__ 方法。

立即學習Python免費學習筆記(深入)”;

__get__ 的工作方式

當訪問某個屬性時,Python解釋器會在類和實例的命名空間中查找該屬性。如果找到的是一個實現了 __get__ 的對象,并且這個對象是“數據描述符”(有 __set__ 或 __delete__)或者“非數據描述符”,那么就會觸發 __get__ 方法。

舉個例子:

class MyDescriptor:     def __get__(self, instance, owner):         print("Getting value")         return 42  class MyClass:     attr = MyDescriptor()  obj = MyClass() print(obj.attr)

輸出為:

Getting value 42

在這個例子中,訪問 obj.attr 實際上調用了 MyDescriptor.__get__ 方法。這里的關鍵點是,描述符必須作為類屬性存在于另一個類中才會生效,不能直接賦值給實例屬性。

__set__ 的作用與限制

如果你希望描述符可以控制屬性的賦值過程,就需要實現 __set__ 方法。這個方法會在你執行類似 obj.attr = value 的操作時被調用。

來看一個簡單的實現:

class MyDescriptor:     def __get__(self, instance, owner):         print("Getting value")         return self.value      def __set__(self, instance, value):         print("Setting value")         self.value = value  class MyClass:     attr = MyDescriptor()  obj = MyClass() obj.attr = 100 print(obj.attr)

輸出:

Setting value Getting value 100

注意幾點:

  • 如果一個描述符沒有實現 __set__,那它就是一個“非數據描述符”,這時候實例字典中可以直接覆蓋這個屬性。
  • 如果描述符同時實現了 __get__ 和 __set__,它就是“數據描述符”,這時實例字典中的同名屬性會被忽略。
  • 描述符的 __set__ 方法通常會把值保存在實例上而不是描述符自身,否則多個實例共享同一個值。

例如,在上面的例子中,如果我們想讓每個 MyClass 實例都有自己的值,應該把值存在 instance 上:

def __set__(self, instance, value):     instance._value = value  def __get__(self, instance, owner):     return instance._value

這樣就能避免不同實例之間的值互相干擾。

常見誤區與注意事項

使用描述符時有幾個常見的坑需要注意:

  • 不要把值存在描述符自己身上:這會導致所有使用該描述符的實例共享同一個值。
  • 確保描述符是類屬性:如果不是放在類中而是賦值給實例,描述符不會起作用。
  • 區分數據描述符和非數據描述符:前者優先級高于實例字典,后者則相反。
  • 避免循環引用:在 __get__ 或 __set__ 中不小心修改了自身屬性,可能會導致無限遞歸

基本上就這些內容了。描述符是Python底層機制的一部分,雖然不常用,但在實現高級特性如類型檢查、自動屬性管理等方面非常有用。掌握其原理,能讓你寫出更靈活、可控的類設計。

? 版權聲明
THE END
喜歡就支持一下吧
點贊14 分享