Python 描述符是什么?以及如何實現


先看一個例子,@property。被@property修飾的成員函數,將變為一個描述符。這是最簡單的創建描述符的方式。

class Foo:
  @property
  def attr(self):
    print('getting attr')
    return 'attr value'

  def bar(self): pass

foo = Foo()

上面這個例子中, attr 是類 Foo 的一個成員函數,可通過語句 foo.attr() 被調用。 但當它被 @property 修飾后,這個成員函數將不再是一個函數,而變為一個描述符。 bar 是一個未被修飾的成員函數。 type(Foo.attr)type(Foo.bar) 的結果分別為:

<type 'property'>
<type 'instancemethod'>

attr 的類型為 property (注:一個 property 類型的對象總是一個描述符), bar 的類型為 instancemethod ,也即一個常規的成員函數。

此時 attr 將無法再被調用,當嘗試調用它時,語句 foo.attr() 將拋出錯誤:

TypeError: 'str' object is not callable

讓我們來理解這個錯誤。

首先來看 foo.attr 的值:

attr value

其類型 type(foo.attr)

str

foo.attr 的類型為 str ,因此便有了以上的錯誤,一個 str 對象無法被調用。其值為'attr value',正好是原始 attr 函數的返回值。 因此語句 foo.attr 實際上觸發了原始 attr 函數的調用,並且將函數的返回值作為其值。實際上語句 print(foo.attr) 的輸出為:

getting attr
attr value

進一步驗證了執行語句 foo.attr 時,原始的 attr 函數被調用。

發生了什么?當執行一個訪問對象屬性的語句 foo.attr 時,結果一個函數調用被觸發!這便是描述符的作用:將屬性訪問轉變為函數調用,並由這個函數來控制這個屬性的值(也即函數的返回值),以及在返回值前做定制化的操作。此時可以給描述符一個簡要定義:

描述符是類的一個屬性,控制類實例對象訪問這個屬性時如何返回值及做哪些額外操作

這留給程序員的空間是巨大的。。

描述符協議

任何實現了描述符協議的類都可以作為描述符類。描述符協議為一組成員函數定義,包括:

函數 作用 返回值 是否必須
__get__(self, obj, type) 獲取屬性值 屬性的值
__set__(self, obj, value) 設置屬性的值 None
__delete__(self, obj) 刪除屬性 None

如果一個類實現了以上成員函數,則它便是一個描述符類,其實例對象便是一個描述符

下面是一個自定義的描述符的實現。

class MyDescriptor:
  def __init__(self):
    self.data = None
  def __get__(self, obj, type):
    print('get called')
    return self.data
  def __set__(self, obj, value):
    print('set called')
    self.data = value
  def __delete__(self, obj):
    print('delete called')
    del self.data

class Foo:
  attr = MyDescriptor()

foo = Foo()

示例中 MyDescriptor 實現了描述符協議(也即實現了 __get__, __set__, __delete__ 函數),因此其為一個描述符類。 Fooattr 屬性為 MyDescriptor 類的實例對象,因此它是一個描述符。

print(foo.attr) 的輸出為:

get called
None

可見當訪問 fooattr 屬性時, MyDescriptor__get__ 函數被調用。

foo.attr = 'new value' 的輸出為:

set called

可見當為 attr 設置一個新值時, MyDescriptor__set__ 函數被調用。

再運行 print(foo.attr) ,輸出為:

get called
new value

可見新值已被設置。

del foo.attr 的輸出為:

delete called

可見當為刪除屬性 attr 時, MyDescriptor__delete__ 函數被調用。

再執行 print(foo.attr)AttributeError 被拋出:

get called
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "1.py", line 6, in __get__
    return self.data
AttributeError: 'MyDescriptor' object has no attribute 'data'

可見屬性 attr 已被刪除。

參數意義

__get__(self, obj, type) 函數各個參數的意義為:

參數 意義 例子中的對應
self 描述符對象本身 Foo.attr
obj 使用描述符的對象實例 foo
type obj的類型 Foo

__set__(self, obj, value) 函數的self和obj參數的意義同 __get__ ,value的意義為:

參數 意義 例子中的對應
value 屬性的新值 'new value'

__delete__(self, obj) 函數的self和obj參數的意義同 __get__

(全文完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM