先看一個例子,@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__
。
(全文完)