python 描述符解析

oliver_lv發表於2016-12-07

什麼是描述符

python描述符是一個“繫結行為”的物件屬性,在描述符協議中,它可以通過方法重寫屬性的訪問。這些方法有 __get__(), __set__(), 和__delete__()。如果這些方法中的任何一個被定義在一個物件中,這個物件就是一個描述符。

描述符的呼叫

描述符作為屬性訪問是被自動呼叫的。

對於類屬性描述符物件,使用type.__getattribute__,它能把Class.x轉換成Class.__dict__[‘x’].__get__(None, Class)。
對於例項屬性描述符物件,使用object.__getattribute__,它能把object.x轉換為type(object).__dict__[‘x’].__get__(object, type(object))。

描述符講解

下面我們具體通過例項來詳細說明描述符的使用

先定義一個描述符

上面實現了__get__和__set__。所以這是一個描述符物件。而且是一個資料描述符物件,非資料描述符物件只實現__get__方法。這2者之間有一些區別,下面會講到。

再定義一個呼叫描述符物件的類

訪問 MyClass.x 輸出

發現訪問x會去呼叫描述符的__get__方法。這就達到了描述符的作用,可以改變物件屬性的訪問,使用描述符的方法。因為如果解析器發現x是一個描述符的話,其實在內部是通過type.__getattribute__(),它能把MyClass.x轉換為MyClass.__dict__[“x”].__get__(None,MyClass)來訪問。

描述符的物件定義為類屬性,如果定義成物件屬性會有什麼不同嗎?下面我們試驗一下

從上面的輸出,可以看到訪問類屬性的確呼叫了描述符的__get__方法,看到輸出的結果是int型別。而呼叫例項屬性並沒有訪問__get__方法。而是直接返回描述符的例項物件。之所以是這樣是因為當訪問一個例項描述符物件時,object.__getattribute__會將test.y轉換為type(test).__dict__[‘y’].__get__(test,type(test))
而MyClass類中沒有“y”屬性,所以無法訪呼叫到_get__方法,這裡會有一個判斷的過程。但這個例項物件仍然是一個描述符物件。所以最好定義描述符物件為類屬性。當然不是不可以定義為例項屬性,請看下面

當定義的類屬性描述符物件和例項屬性有相同的名字時

然後呼叫

可見依然呼叫了描述符的方法。按照常理,應該訪問 test.__dict__[‘x’],然後是type(test).__dict__[‘x’]。由於我們定義了例項屬性x。應該只輸出100。可這裡從輸出結果看的的確確的訪問了描述符的方法。那麼這是為什麼呢?

其實這裡主要是因為當python發現例項物件的字典中有與定義的描述符有相同名字的物件時,描述符優先,會覆蓋掉例項屬性。python會改寫預設的行為,去呼叫描述符的方法來代替。我們可以輸出類和例項物件的字典看看

從輸出中發現例項物件的字典中根本就沒有x物件,即使我們在類中定義了self.x。而類的字典中則有x描述符物件。這主要就是因為描述符優先。

上面我們定義的描述符有__get__和__set__2個方法,所以是一個資料描述符,非資料描述符只有一個__get__方法,通常用於方法。此外,非資料描述符的優先順序低於例項屬性。下面看一個例子,我們去掉__set__方法。

從上面的輸出,可以看出非資料描述符不會覆蓋掉例項屬性。而且優先順序比例項屬性低。這也是和資料描述符的一個區別。

綜上所述,對於描述符的呼叫有以下幾點需要注意

  1. 描述符被 getattribute 方法呼叫
  2. 覆蓋__getattribute__會讓描述符無法自動呼叫
  3. 描述符只適用於新式類,即繼承object的類
  4. object . getattribute 和 type . getattribute 呼叫__get__方法不一樣
  5. 資料描述符優先於例項的字典,對於相同名字的會覆蓋
  6. 例項的字典優先於非資料描述符。但不會覆蓋。
  7. 對於資料描述符,python中property就是一個典型的應用。

對於非資料描述符,其主要用於方法。如靜態方法和類方法。看原始碼可以看到只實現了描述符協議中的__get__方法,而沒有實現__set__和__del__。

如下面這樣模擬靜態方法

呼叫MyClass.get_x(100)相當於

我們知道在python中,一切皆是物件。每一個定義的方法其實都是一個物件。在這裡我們可以通過dir()檢視每一個方法裡的屬性和方法。看下面

從dir的輸出,可以看到,每個方法物件都包含一個__get__方法。因此可以說每一個方法都是一個非資料描述符。通常我們通過點操作符呼叫方法時,內部都是呼叫這個__get__方法。

參考 https://docs.python.org/2.7/h…

以上就是本人對描述符的一些理解,有什麼不正確的地方還請不吝指出,謝謝!

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

python 描述符解析 python 描述符解析

相關文章