python __set__ __get__ 等解釋

若是你和我同樣,曾經對method和function以及對它們的各類訪問方式包括self參數的隱含傳遞疑惑不解,建議你耐心的看下去。這裏還提到了Python屬性查找策略,使你清楚的知道Python處理obj.attr和obj.attr=val時,到底作了哪些工做。

Python中,對象的方法也是也能夠認爲是屬性,因此下面所說的屬性包含方法在內。python

先定義下面這個類,還定義了它的一個實例,留着後面用。函數

Python代碼  收藏代碼spa

  1. class T(object):  orm

  2.     name = 'name'  對象

  3.     def hello(self):  ip

  4.         print 'hello'  字符串

  5. t = T()   get

使用dir(t)列出t的全部有效屬性:string

Python代碼  收藏代碼hash

  1. >>> dir(t)  

  2. ['__class__''__delattr__''__dict__''__doc__''__getattribute__',  

  3.  '__hash__''__init__''__module__''__new__''__reduce__''__reduce_ex__',  

  4.  '__repr__''__setattr__''__str__''__weakref__''hello''name']  


屬性能夠分爲兩類,一類是Python自動產生的,如__class__,__hash__等,另外一類是咱們自定義的,如上面的hello,name。咱們只關心自定義屬性。
類和實例對象(實際上,Python中一切都是對象,類是type的實例)都有__dict__屬性,裏面存放它們的自定義屬性(對與類,裏面還存放了別的東西)。

Python代碼  收藏代碼

  1. >>> t.__dict__  

  2. {}  

  3. >>> T.__dict__  

  4. <dictproxy object at 0x00CD0FF0>  

  5. >>> dict(T.__dict__)            #因爲T.__dict__並無直接返回dict對象,這裏進行轉換,以方便觀察其中的內容  

  6. {'__module__''__main__''name''name',  

  7.  'hello': <function hello at 0x00CC2470>,  

  8.  '__dict__': <attribute '__dict__' of 'T' objects>,  

  9.  '__weakref__': <attribute '__weakref__' of 'T' objects>, '__doc__'None}  

  10. >>>   

有些內建類型,如list和string,它們沒有__dict__屬性,隨意沒辦法在它們上面附加自定義屬性。

 

到如今爲止t.__dict__是一個空的字典,由於咱們並無在t上自定義任何屬性,它的有效屬性hello和name都是從T獲得的。T的__dict__中包含hello和name。當遇到t.name語句時,Python怎麼找到t的name屬性呢?

首先,Python判斷name屬性是不是個自動產生的屬性,若是是自動產生的屬性,就按特別的方法找到這個屬性,固然,這裏的name不是自動產生的屬性,而是咱們本身定義的,Python因而到t的__dict__中尋找。仍是沒找到。

接着,Python找到了t所屬的類T,搜索T.__dict__,指望找到name,很幸運,直接找到了,因而返回name的值:字符串‘name’。若是在T.__dict__中尚未找到,Python會接着到T的父類(若是T有父類的話)的__dict__中繼續查找。

 

這不足以解決咱們的困惑,由於事情遠沒有這麼簡單,上面說的實際上是個簡化的步驟。

繼續上面的例子,對於name屬性T.name和T.__dict__['name']是徹底同樣的。

Python代碼  收藏代碼

  1. >>> T.name  

  2. 'name'  

  3. >>> T.__dict__['name']  

  4. 'name'  

  5. >>>   

可是對於hello,情形就有些不一樣了

Python代碼  收藏代碼

  1. >>> T.hello  

  2. <unbound method T.hello>  

  3. >>> T.__dict__['hello']  

  4. <function hello at 0x00CC2470>  

  5. >>>   

能夠發現,T.hello是個unbound method。而T.__dict__['hello']是個函數(不是方法)。

推斷:方法在類的__dict__中是以函數的形式存在的(方法的定義和函數的定義簡直同樣,除了要把第一個參數設爲self)。那麼T.hello獲得的應該也是個函數啊,怎麼成了unbound method了。

再看看從實例t中訪問hello

Python代碼  收藏代碼

  1. >>> t.hello  

  2. <bound method T.hello of <__main__.T object at 0x00CD0E50>>  

  3. >>>   

是一個bound method。

有意思,按照上面的查找策略,既然在T的__dict__中hello是個函數,那麼T.hello和t.hello應該都是同一個函數纔對。究竟是怎麼變成方法的,並且還分爲unbound method和bound method。

關於unbound和bound到還好理解,咱們不妨先做以下設想:方法是要從實例調用的嘛(指實例方法,classmethod和staticmethod後面講),若是從類中訪問,如T.hello,hello沒有和任何實例發生聯繫,也就是沒綁定(unbound)到任何實例上,因此是個unbound,對t.hello的訪問方式,hello和t發生了聯繫,所以是bound。

但從函數<function hello at 0x00CC2470>到方法<unbound method T.hello>的確讓人費解。

 

一切的魔法都源自今天的主角:descriptor

 

查找屬性時,如obj.attr,若是Python發現這個屬性attr有個__get__方法,Python會調用attr的__get__方法,返回__get__方法的返回值,而不是返回attr(這一句話並不許確,我只是但願你能對descriptor有個初步的概念)。

Python中iterator(怎麼扯到Iterator了?)是實現了iterator協議的對象,也就是說它實現了下面兩個方法__iter__和next()。相似的,descriptor也是實現了某些特定方法的對象。descriptor的特定方法是__get__,__set__和__delete__,其中__set__和__delete__方法是可選的。iterator必須依附某個對象而存在(由對象的__iter__方法返回),descriptor也必須依附對象,做爲對象的一個屬性,它而不能單獨存在。還有一點,descriptor必須存在於類的__dict__中,這句話的意思是隻有在類的__dict__中找到屬性,Python纔會去看看它有沒有__get__等方法,對一個在實例的__dict__中找到的屬性,Python根本不理會它有沒有__get__等方法,直接返回屬性自己。descriptor究竟是什麼呢:簡單的說,descriptor是對象的一個屬性,只不過它存在於類的__dict__中而且有特殊方法__get__(可能還有__set__和__delete)而具備一點特別的功能,爲了方便指代這樣的屬性,咱們給它起了個名字叫descriptor屬性。

可能你仍是不明白,下面開始用例子說明。

先定義這個類:

Python代碼  收藏代碼

  1. class Descriptor(object):  

  2.     def __get__(self, obj, type=None):  

  3.             return 'get'self, obj, type  

  4.     def __set__(self, obj, val):  

  5.         print 'set'self, obj, val  

  6.     def __delete__(self, obj):  

  7.         print 'delete'self, obj  

這裏__set__和__delete__其實能夠不出現,不過爲了後面的說明,暫時把它們全寫上。

下面解釋一下三個方法的參數:

self固然不用說,指的是當前Descriptor的實例。obj值擁有屬性的對象。這應該不難理解,前面已經說了,descriptor是對象的稍微有點特殊的屬性,這裏的obj就是擁有它的對象,要注意的是,若是是直接用類訪問descriptor(別嫌囉嗦,descriptor是個屬性,直接用類訪問descriptor就是直接用類訪問類的屬性),obj的值是None。type是obj的類型,剛纔說過,若是直接經過類訪問descriptor,obj是None,此時type就是類自己。

三個方法的意義,假設T是一個類,t是它的一個實例,d是T的一個descriptor屬性(牛什麼啊,不就是有個__get__方法嗎!),value是一個有效值:

讀取屬性時,如T.d,返回的是d.__get__(None, T)的結果,t.d返回的是d.__get__(t, T)的結果。

設置屬性時,t.d = value,實際上調用d.__set__(t, value),T.d = value,這是真正的賦值,T.d的值今後變成value。刪除屬性和設置屬性相似。

下面用例子說明,看看Python中執行是怎麼樣的:

從新定義咱們的類T和實例t

Python代碼  收藏代碼

  1. class T(object):  

  2.     d = Descriptor()  

  3. t = T()  

 d是T的類屬性,做爲Descriptor的實例,它有__get__等方法,顯然,d知足了全部的條件,如今它就是一個descriptor!

Python代碼  收藏代碼

  1. >>> t.d         #t.d,返回的實際是d.__get__(t, T)  

  2. ('get', <__main__.Descriptor object at 0x00CD9450>, <__main__.T object at 0x00CD0E50>, <class '__main__.T'>)  

  3. >>> T.d        #T.d,返回的實際是d.__get__(None, T),因此obj的位置爲None  

  4. ('get', <__main__.Descriptor object at 0x00CD9450>, None, <class '__main__.T'>)  

  5. >>> t.d = 'hello'   #在實例上對descriptor設置值。要注意的是,如今顯示不是返回值,而是__set__方法中  

  6.                                print語句輸出的。  

  7. set <__main__.Descriptor object at 0x00CD9450> <__main__.T object at 0x00CD0E50> hello  

  8. >>> t.d         #可見,調用了Python調用了__set__方法,並無改變t.d的值  

  9. ('get', <__main__.Descriptor object at 0x00CD9450>, <__main__.T object at 0x00CD0E50>, <class '__main__.T'>)  

  10. >>> T.d = 'hello'   #沒有調用__set__方法  

  11. >>> T.d                #確實改變了T.d的值  

  12. 'hello'  

  13. >>> t.d               #t.d的值也變了,這能夠理解,按咱們上面說的屬性查找策略,t.d是從T.__dict__中獲得的  

  14.                               T.__dict__['d']的值是'hello',t.d固然也是'hello'  

  15. 'hello'  

data descriptor和non-data descriptor

象上面的d,同時具備__get__和__set__方法,這樣的descriptor叫作data descriptor,若是隻有__get__方法,則叫作non-data descriptor。容易想到,因爲non-data descriptor沒有__set__方法,因此在經過實例對屬性賦值時,例如上面的t.d = 'hello',不會再調用__set__方法,會直接把t.d的值變成'hello'嗎?口說無憑,實例爲證:

Python代碼  收藏代碼

  1. class Descriptor(object):  

  2.     def __get__(self, obj, type=None):  

  3.             return 'get'self, obj, type  

  4. class T(object):  

  5.        d = Descriptor()  

  6. t = T()  

 

Python代碼  收藏代碼

  1. >>> t.d  

  2. ('get', <__main__.Descriptor object at 0x00CD9550>, <__main__.T object at 0x00CD9510>, <class '__main__.T'>)  

  3. >>> t.d = 'hello'  

  4. >>> t.d  

  5. 'hello'  

  6. >>>   

在實例上對non-data descriptor賦值隱藏了實例上的non-data descriptor!

 

是時候坦白真正詳細的屬性查找策略 了,對於obj.attr(注意:obj能夠是一個類):

1.若是attr是一個Python自動產生的屬性,找到!(優先級很是高!)

2.查找obj.__class__.__dict__,若是attr存在而且是data descriptor,返回data descriptor的__get__方法的結果,若是沒有繼續在obj.__class__的父類以及祖先類中尋找data descriptor

3.在obj.__dict__中查找,這一步分兩種狀況,第一種狀況是obj是一個普通實例,找到就直接返回,找不到進行下一步。第二種狀況是obj是一個類,依次在obj和它的父類、祖先類的__dict__中查找,若是找到一個descriptor就返回descriptor的__get__方法的結果,不然直接返回attr。若是沒有找到,進行下一步。

4.在obj.__class__.__dict__中查找,若是找到了一個descriptor(插一句:這裏的descriptor必定是non-data descriptor,若是它是data descriptor,第二步就找到它了)descriptor的__get__方法的結果。若是找到一個普通屬性,直接返回屬性值。若是沒找到,進行下一步。

5.很不幸,Python終於受不了。在這一步,它raise AttributeError

 

利用這個,咱們簡單分析一下上面爲何要強調descriptor要在類中才行。咱們感興趣的查找步驟是2,3,4。第2步和第4步都是在類中查找。對於第3步,若是在普通實例中找到了,直接返回,沒有判斷它有沒有__get__()方法。

 

對屬性賦值時的查找策略 ,對於obj.attr = value

1.查找obj.__class__.__dict__,若是attr存在而且是一個data descriptor,調用attr的__set__方法,結束。若是不存在,會繼續到obj.__class__的父類和祖先類中查找,找到 data descriptor則調用其__set__方法。沒找到則進入下一步。

2.直接在obj.__dict__中加入obj.__dict__['attr'] = value

 

順便分析下爲何在實例上對non-data descriptor賦值隱藏了實例上的non-data descriptor。

接上面的non-data descriptor例子

Python代碼  收藏代碼

  1. >>> t.__dict__  

  2. {'d''hello'}  

 在t的__dict__裏出現了d這個屬性。根據對屬性賦值的查找策略,第1步,確實在t.__class__.__dict__也就是T.__dict__中找到了屬性d,但它是一個non-data descriptor,不知足data descriptor的要求,進入第2步,直接在t的__dict__屬性中加入了屬性和屬性值。當獲取t.d時,執行查找策略,第2步在T.__dict__中找到了d,但它是non-data descriptor,步知足要求,進行第3步,在t的__dict__中找到了d,直接返回了它的值'hello'。

 

說了這麼半天,還沒到函數和方法!

算了,明天在說吧

簡單提一下,全部的函數(方法)都有__get__方法,當它們在類的__dict__中是,它們就是non-data descriptor。

相關文章
相關標籤/搜索