|  如果你和我一樣,曾經(jīng)對method和function以及對它們的各種訪問方式包括self參數(shù)的隱含傳遞迷惑不解,建議你耐心的看下去。這里還提到了Python屬性查找策略,使你清楚的知道Python處理obj.attr和obj.attr=val時,到底做了哪些工作。 先定義下面這個類,還定義了它的一個實(shí)例,留著后面用。 使用dir(t)列出t的所有有效屬性: 
 
 
屬性可以分為兩類,一類是Python自動產(chǎn)生的,如__class__,__hash__等,另一類是我們自定義的,如上面的hello,name。我們只關(guān)心自定義屬性。 
 有些內(nèi)建類型,如list和string,它們沒有__dict__屬性,隨意沒辦法在它們上面附加自定義屬性。 
 到現(xiàn)在為止t.__dict__是一個空的字典,因?yàn)槲覀儾]有在t上自定義任何屬性,它的有效屬性hello和name都是從T得到的。T的__dict__中包含hello和name。當(dāng)遇到t.name語句時,Python怎么找到t的name屬性呢? 首先,Python判斷name屬性是否是個自動產(chǎn)生的屬性,如果是自動產(chǎn)生的屬性,就按特別的方法找到這個屬性,當(dāng)然,這里的name不是自動產(chǎn)生的屬性,而是我們自己定義的,Python于是到t的__dict__中尋找。還是沒找到。 接著,Python找到了t所屬的類T,搜索T.__dict__,期望找到name,很幸運(yùn),直接找到了,于是返回name的值:字符串‘name’。如果在T.__dict__中還沒有找到,Python會接著到T的父類(如果T有父類的話)的__dict__中繼續(xù)查找。 
 這不足以解決我們的困惑,因?yàn)槭虑檫h(yuǎn)沒有這么簡單,上面說的其實(shí)是個簡化的步驟。 
 繼續(xù)上面的例子,對于name屬性T.name和T.__dict__['name']是完全一樣的。 但是對于hello,情形就有些不同了 
 可以發(fā)現(xiàn),T.hello是個unbound method。而T.__dict__['hello']是個函數(shù)(不是方法)。 推斷:方法在類的__dict__中是以函數(shù)的形式存在的(方法的定義和函數(shù)的定義簡直一樣,除了要把第一個參數(shù)設(shè)為self)。那么T.hello得到的應(yīng)該也是個函數(shù)啊,怎么成了unbound method了。 再看看從實(shí)例t中訪問hello 是一個bound method。 有意思,按照上面的查找策略,既然在T的__dict__中hello是個函數(shù),那么T.hello和t.hello應(yīng)該都是同一個函數(shù)才對。到底是怎么變成方法的,而且還分為unbound method和bound method。 關(guān)于unbound和bound到還好理解,我們不妨先作如下設(shè)想:方法是要從實(shí)例調(diào)用的嘛(指實(shí)例方法,classmethod和staticmethod后面講),如果從類中訪問,如T.hello,hello沒有和任何實(shí)例發(fā)生聯(lián)系,也就是沒綁定(unbound)到任何實(shí)例上,所以是個unbound,對t.hello的訪問方式,hello和t發(fā)生了聯(lián)系,因此是bound。 但從函數(shù)<function hello at 0x00CC2470>到方法<unbound method T.hello>的確讓人費(fèi)解。 
 一切的魔法都源自今天的主角:descriptor 
 查找屬性時,如obj.attr,如果Python發(fā)現(xiàn)這個屬性attr有個__get__方法,Python會調(diào)用attr的__get__方法,返回__get__方法的返回值,而不是返回attr(這一句話并不準(zhǔn)確,我只是希望你能對descriptor有個初步的概念)。 Python中iterator(怎么扯到Iterator了?)是實(shí)現(xiàn)了iterator協(xié)議的對象,也就是說它實(shí)現(xiàn)了下面兩個方法__iter__和next()。類似的,descriptor也是實(shí)現(xiàn)了某些特定方法的對象。descriptor的特定方法是__get__,__set__和__delete__,其中__set__和__delete__方法是可選的。iterator必須依附某個對象而存在(由對象的__iter__方法返回),descriptor也必須依附對象,作為對象的一個屬性,它而不能單獨(dú)存在。還有一點(diǎn),descriptor必須存在于類的__dict__中,這句話的意思是只有在類的__dict__中找到屬性,Python才會去看看它有沒有__get__等方法,對一個在實(shí)例的__dict__中找到的屬性,Python根本不理會它有沒有__get__等方法,直接返回屬性本身。descriptor到底是什么呢:簡單的說,descriptor是對象的一個屬性,只不過它存在于類的__dict__中并且有特殊方法__get__(可能還有__set__和__delete)而具有一點(diǎn)特別的功能,為了方便指代這樣的屬性,我們給它起了個名字叫descriptor屬性。 可能你還是不明白,下面開始用例子說明。 先定義這個類: 
 這里__set__和__delete__其實(shí)可以不出現(xiàn),不過為了后面的說明,暫時把它們?nèi)珜懮稀?/p> 下面解釋一下三個方法的參數(shù): self當(dāng)然不用說,指的是當(dāng)前Descriptor的實(shí)例。obj值擁有屬性的對象。這應(yīng)該不難理解,前面已經(jīng)說了,descriptor是對象的稍微有點(diǎn)特殊的屬性,這里的obj就是擁有它的對象,要注意的是,如果是直接用類訪問descriptor(別嫌啰嗦,descriptor是個屬性,直接用類訪問descriptor就是直接用類訪問類的屬性),obj的值是None。type是obj的類型,剛才說過,如果直接通過類訪問descriptor,obj是None,此時type就是類本身。 三個方法的意義,假設(shè)T是一個類,t是它的一個實(shí)例,d是T的一個descriptor屬性(牛什么啊,不就是有個__get__方法嗎!),value是一個有效值: 讀取屬性時,如T.d,返回的是d.__get__(None, T)的結(jié)果,t.d返回的是d.__get__(t, T)的結(jié)果。 設(shè)置屬性時,t.d = value,實(shí)際上調(diào)用d.__set__(t, value),T.d = value,這是真正的賦值,T.d的值從此變成value。刪除屬性和設(shè)置屬性類似。 下面用例子說明,看看Python中執(zhí)行是怎么樣的: 重新定義我們的類T和實(shí)例t d是T的類屬性,作為Descriptor的實(shí)例,它有__get__等方法,顯然,d滿足了所有的條件,現(xiàn)在它就是一個descriptor! 
 data descriptor和non-data descriptor 象上面的d,同時具有__get__和__set__方法,這樣的descriptor叫做data descriptor,如果只有__get__方法,則叫做non-data descriptor。容易想到,由于non-data descriptor沒有__set__方法,所以在通過實(shí)例對屬性賦值時,例如上面的t.d = 'hello',不會再調(diào)用__set__方法,會直接把t.d的值變成'hello'嗎?口說無憑,實(shí)例為證: 
 
 在實(shí)例上對non-data descriptor賦值隱藏了實(shí)例上的non-data descriptor! 
 是時候坦白真正詳細(xì)的屬性查找策略 了,對于obj.attr(注意:obj可以是一個類): 1.如果attr是一個Python自動產(chǎn)生的屬性,找到!(優(yōu)先級非常高!) 2.查找obj.__class__.__dict__,如果attr存在并且是data descriptor,返回data descriptor的__get__方法的結(jié)果,如果沒有繼續(xù)在obj.__class__的父類以及祖先類中尋找data descriptor 3.在obj.__dict__中查找,這一步分兩種情況,第一種情況是obj是一個普通實(shí)例,找到就直接返回,找不到進(jìn)行下一步。第二種情況是obj是一個類,依次在obj和它的父類、祖先類的__dict__中查找,如果找到一個descriptor就返回descriptor的__get__方法的結(jié)果,否則直接返回attr。如果沒有找到,進(jìn)行下一步。 4.在obj.__class__.__dict__中查找,如果找到了一個descriptor(插一句:這里的descriptor一定是non-data descriptor,如果它是data descriptor,第二步就找到它了)descriptor的__get__方法的結(jié)果。如果找到一個普通屬性,直接返回屬性值。如果沒找到,進(jìn)行下一步。 5.很不幸,Python終于受不了。在這一步,它raise AttributeError 
 利用這個,我們簡單分析一下上面為什么要強(qiáng)調(diào)descriptor要在類中才行。我們感興趣的查找步驟是2,3,4。第2步和第4步都是在類中查找。對于第3步,如果在普通實(shí)例中找到了,直接返回,沒有判斷它有沒有__get__()方法。 
 
對屬性賦值時的查找策略 ,對于obj.attr = value 1.查找obj.__class__.__dict__,如果attr存在并且是一個data descriptor,調(diào)用attr的__set__方法,結(jié)束。如果不存在,會繼續(xù)到obj.__class__的父類和祖先類中查找,找到 data descriptor則調(diào)用其__set__方法。沒找到則進(jìn)入下一步。 2.直接在obj.__dict__中加入obj.__dict__['attr'] = value 
 順便分析下為什么在實(shí)例上對non-data descriptor賦值隱藏了實(shí)例上的non-data descriptor。 接上面的non-data descriptor例子 在t的__dict__里出現(xiàn)了d這個屬性。根據(jù)對屬性賦值的查找策略,第1步,確實(shí)在t.__class__.__dict__也就是T.__dict__中找到了屬性d,但它是一個non-data descriptor,不滿足data descriptor的要求,進(jìn)入第2步,直接在t的__dict__屬性中加入了屬性和屬性值。當(dāng)獲取t.d時,執(zhí)行查找策略,第2步在T.__dict__中找到了d,但它是non-data descriptor,步滿足要求,進(jìn)行第3步,在t的__dict__中找到了d,直接返回了它的值'hello'。 
 說了這么半天,還沒到函數(shù)和方法! 算了,明天在說吧 簡單提一下,所有的函數(shù)(方法)都有__get__方法,當(dāng)它們在類的__dict__中是,它們就是non-data descriptor。 | 
|  |