先看一個例子,@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__ 函數),因此其為一個描述符類。 Foo 的 attr 屬性為 MyDescriptor 類的實例對象,因此它是一個描述符。
print(foo.attr) 的輸出為:
get called
None
可見當訪問 foo 的 attr 屬性時, 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__ 。
(全文完)
