很少有人會(huì)去刻意關(guān)注描述符 (Descriptor),盡管它時(shí)時(shí)刻刻以屬性、方法的身份出現(xiàn)。
描述符協(xié)議:
__get__(self, instance, owner) --> return value
__set__(self, instance, value)
__delete__(self, instance)
描述符對(duì)象以類型 (owner class) 成員的方式出現(xiàn),且最少要實(shí)現(xiàn)一個(gè)協(xié)議方法。最常見的描述符有 property、staticmethod、classsmethod。訪問描述符類型成員時(shí),解釋器會(huì)自動(dòng)調(diào)用與行為相對(duì)應(yīng)的協(xié)議方法。
>>> class MyDescriptor(object):
... def __get__(self, instance, owner): # 本例中 owner 是 class Data。
... print "get:", instance, owner
... return hex(id(instance))
...
... def __set__(self, instance, value):
... print "set:", instance, value
...
... def __delete__(self, instance):
... print "del:", instance
>>> class Data(object):
... x = MyDescriptor()
>>> d = Data()
>>> d.x # __get__ 的返回值。
get: <__main__.Data object at 0x107a23790> <class '__main__.Data'>
'0x107a23790'
>>> d.x = 100 # d 被當(dāng)做 instance 實(shí)參。
set: <__main__.Data object at 0x107a23790> 100
>>> del d.x # d 被當(dāng)做 instance 實(shí)參。
del: <__main__.Data object at 0x107a23790>
>>> Data.x # 以 owner 類型訪問時(shí),__get__ 有效。
get: None <class '__main__.Data'> # instance = None
'0x106a96148'
>>> Data.x = 1 # __set__ 對(duì) class 調(diào)用無(wú)效。
# 因此 Data.x 被重新賦值。
>>> type(Data.x)
<type 'int'>
如果沒有定義 get 方法,那么直接返回描述符對(duì)象,不會(huì)有默認(rèn) get 實(shí)現(xiàn)。
property
屬性總是 data descriptor,這和是否提供 setter 無(wú)關(guān)。其優(yōu)先級(jí)總是高過(guò)同名實(shí)例字段,如果沒有提供 setter,set 方法會(huì)阻止賦值操作。
>>> class Data(object):
... oid = property(lambda s: hex(id(s)))
>>> hasattr(Data.oid, "__set__")
True
>>> d = Data()
>>> d.oid
'0x107a23a90'
>>> d.oid = 123
AttributeError: can't set attribute
non-data
non-data descriptor 會(huì)被同名實(shí)例字段搶先。
>>> class Descriptor(object):
... def __get__(self, instance, owner):
... print "__get__"
>>> class Data(object):
... x = Descriptor()
>>> d = Data()
>>> d.x # 描述符有效。
__get__
>>> d.__dict__ # instance.__dict__ 沒有同名字段。
{}
>>> d.x = 123 # 沒有 __set__,創(chuàng)建同名實(shí)例字段。
>>> d.__dict__
{'x': 123}
>>> d.x # 依據(jù)成員查找規(guī)則,實(shí)例字段被優(yōu)先命中。
123
>>> Data.x # 描述符在 owner_class.__dict___。
__get__
bound method
通過(guò)描述符,我們可以了解實(shí)例方法 self 參數(shù)是如何隱式傳遞的。
>>> class Data(object):
... def test(self): print "test"
>>> d = Data()
>>> d.test # 只有 bound method 才會(huì)隱式傳遞 self。
<bound method Data.test of <__main__.Data object at 0x10740b050>>
>>> Data.test.__get__(d, Data) # 向 __get__ 傳遞 instance 參數(shù)。
<bound method Data.test of <__main__.Data object at 0x10740b050>>
>>> Data.test # unbound method 需顯式傳遞 self。
<unbound method Data.test>
>>> Data.test.__get__(None, Data) # instance 為 None。
<unbound method Data.test>
現(xiàn)在可以看出,bound/unbound 是 get 造成的,關(guān)鍵就是 instance 參數(shù)。那么 self 參數(shù)存在哪?由誰(shuí)替我們自動(dòng)傳遞 self 參數(shù)呢?
>>> bm = Data.test.__get__(d, Data)
>>> bm.__func__ # 實(shí)際的目標(biāo)函數(shù) test。
<function test at 0x107404488>
>>> bm.__self__ # __get__ instance 參數(shù),也就是 self。
<__main__.Data object at 0x10740b050>
>>> bm.__call__() # __call__ 內(nèi)部替我們傳遞 self !
test
>>> unbm = Data.test.__get__(None, Data) # unbound method
>>> unbm.__func__
<function test at 0x107404488>
>>> unbm.__self__ is None # instance == None, self == None。
True
>>> unbm.__call__() # __call__ 會(huì)檢查 __self__。
TypeError: unbound method test() must be called with Data instance as first argument
(got nothing instead)
>>> unbm.__call__(d) # 只好給 __call__ 有效的 instance。
test
classmethod
不同于 staticmethod,classmethod 會(huì) bound 類型對(duì)象。
>>> class Data(object):
... @classmethod
... def test(cls): print cls
>>> Data.test.__get__(None, Data)
<bound method type.test of <class '__main__.Data'>>
>>> m = Data.test.__get__(None, Data)
>>> m.__self__ # 類型對(duì)象,也就是隱式 cls 參數(shù)。
<class '__main__.Data'>
>>> m.__call__()
<class '__main__.Data'>