python __set__ __get__
如果你和我一樣,曾經對method和function以及對它們的各種訪問方式包括self引數的隱含傳遞迷惑不解,建議你耐心的看下去。這裡還提到了Python屬性查詢策略,使你清楚的知道Python處理obj.attr和obj.attr=val時,到底做了哪些工作。
Python中,物件的方法也是也可以認為是屬性,所以下面所說的屬性包含方法在內。
先定義下面這個類,還定義了它的一個例項,留著後面用。
Python程式碼-
class -
name = ’name’ -
def hello(self): -
print ‘hello’ -
t = T()
使用dir(t)列出t的所有有效屬性:
-
>>> dir(t) -
[’class’, ’delattr’, ’dict’, ’doc’, ’getattribute’, -
’hash’, ’init’, ’module’, ’new’, ’reduce’, ’reduce_ex -
’repr’, ’setattr’, ’str’, ’weakref’, ‘hello’, ‘name’]
屬性可以分為兩類,一類是Python自動產生的,如__class__,__hash__等,另一類是我們自定義的,如上面的hello,name。我們只關心自定義屬性。
類和例項物件(實際上,Python中一切都是物件,類是type的例項)都有__dict__屬性,裡面存放它們的自定義屬性(對與類,裡面還存放了別的東西)。
-
>>> t.dict -
{} -
>>> T.dict -
<dictproxy object at 0x00CD0FF0> -
>>> dict(T.dict) #由於T.dict並沒有直接返回dict物件,這裡進行轉換,以方便觀察其中的內容 -
{’module’: ’main’, ‘name’: ‘name’, -
’hello’: <function hello at 0x00CC2470>, -
’dict’: <attribute ’dict’ of ‘T’ objects>, -
’weakref’: <attribute ’weakref’ of ‘T’ objects>, ’doc’: None} -
>>>
有些內建型別,如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程式碼-
>>> T.name -
’name’ -
>>> T.dict[’name’] -
’name’ -
>>>
但是對於hello,情形就有些不同了
Python程式碼-
>>> T.hello -
<unbound method T.hello> -
>>> T.dict[’hello’] -
<function hello at 0x00CC2470> -
>>>
可以發現,T.hello是個unbound method。而T.__dict__[‘hello’]是個函式(不是方法)。
推斷:方法在類的__dict__中是以函式的形式存在的(方法的定義和函式的定義簡直一樣,除了要把第一個引數設為self)。那麼T.hello得到的應該也是個函式啊,怎麼成了unbound method了。
再看看從例項t中訪問hello
Python程式碼-
>>> t.hello -
<bound method T.hello of <main.T object at 0x00CD0E50>> -
>>>
是一個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程式碼-
class Descriptor(object): -
def get(self, obj, type=None): -
return ‘get’, self, obj, type -
def set(self, obj, val): -
print ‘set’, self, obj, val -
def delete(self, obj): -
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程式碼-
class T(object): -
d = Descriptor() -
t = T()
d是T的類屬性,作為Descriptor的例項,它有__get__等方法,顯然,d滿足了所有的條件,現在它就是一個descriptor!
Python程式碼-
>>> t.d #t.d,返回的實際是d.get(t, T) -
(’get’, <main.Descriptor object at 0x00CD9450>, <main.T object at 0x00CD0E50>, <class ’main.T’>) -
>>> T.d #T.d,返回的實際是d.get(None, T),所以obj的位置為None -
(’get’, <main.Descriptor object at 0x00CD9450>, None, <class ’main.T’>) -
>>> t.d = ’hello’ #在例項上對descriptor設定值。要注意的是,現在顯示不是返回值,而是set方法中 -
print語句輸出的。 -
set <main.Descriptor object at 0x00CD9450> <main.T object at 0x00CD0E50> hello -
>>> t.d #可見,呼叫了Python呼叫了set方法,並沒有改變t.d的值 -
(’get’, <main.Descriptor object at 0x00CD9450>, <main.T object at 0x00CD0E50>, <class ’main.T’>) -
>>> T.d = ’hello’ #沒有呼叫set方法 -
>>> T.d #確實改變了T.d的值 -
’hello’ -
>>> t.d #t.d的值也變了,這可以理解,按我們上面說的屬性查詢策略,t.d是從T.dict中得到的 -
T.dict[’d’]的值是‘hello’,t.d當然也是‘hello’ -
’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程式碼-
class Descriptor(object): -
def get(self, obj, type=None): -
return ‘get’, self, obj, type -
class T(object): -
d = Descriptor() -
t = T()
Python程式碼
-
>>> t.d -
(’get’, <main.Descriptor object at 0x00CD9550>, <main.T object at 0x00CD9510>, <class ’main.T’>) -
>>> t.d = ’hello’ -
>>> t.d -
’hello’ -
>>>
在例項上對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程式碼-
>>> t.dict -
{’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。
轉自:http://blog.csdn.net/huithe/article/details/7484606
但這個也非原創,也是轉載的,裡面並沒有標明原創地址。
如果你和我一樣,曾經對method和function以及對它們的各種訪問方式包括self引數的隱含傳遞迷惑不解,建議你耐心的看下去。這裡還提到了Python屬性查詢策略,使你清楚的知道Python處理obj.attr和obj.attr=val時,到底做了哪些工作。
Python中,物件的方法也是也可以認為是屬性,所以下面所說的屬性包含方法在內。
先定義下面這個類,還定義了它的一個例項,留著後面用。
Python程式碼-
class T(object): -
name = ’name’ -
def hello(self): -
print ‘hello’ -
t = T()
使用dir(t)列出t的所有有效屬性:
Python程式碼-
>>> dir(t) -
[’class’, ’delattr’, ’dict’, ’doc’, ’getattribute’, -
’hash’, ’init’, ’module’, ’new’, ’reduce’, ’reduce_ex’, -
’repr’, ’setattr’, ’str’, ’weakref’, ‘hello’, ‘name’]
屬性可以分為兩類,一類是Python自動產生的,如__class__,__hash__等,另一類是我們自定義的,如上面的hello,name。我們只關心自定義屬性。
類和例項物件(實際上,Python中一切都是物件,類是type的例項)都有__dict__屬性,裡面存放它們的自定義屬性(對與類,裡面還存放了別的東西)。
-
>>> t.dict -
{} -
>>> T.dict -
<dictproxy object at 0x00CD0FF0> -
>>> dict(T.dict) #由於T.dict並沒有直接返回dict物件,這裡進行轉換,以方便觀察其中的內容 -
{’module’: ’main’, ‘name’: ‘name’, -
’hello’: <function hello at 0x00CC2470>, -
’dict’: <attribute ’dict’ of ‘T’ objects>, -
’weakref’: <attribute ’weakref’ of ‘T’ objects>, ’doc’: None} -
>>>
有些內建型別,如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程式碼-
>>> T.name -
’name’ -
>>> T.dict[’name’] -
’name’ -
>>>
但是對於hello,情形就有些不同了
Python程式碼-
>>> T.hello -
<unbound method T.hello> -
>>> T.dict[’hello’] -
<function hello at 0x00CC2470> -
>>>
可以發現,T.hello是個unbound method。而T.__dict__[‘hello’]是個函式(不是方法)。
推斷:方法在類的__dict__中是以函式的形式存在的(方法的定義和函式的定義簡直一樣,除了要把第一個引數設為self)。那麼T.hello得到的應該也是個函式啊,怎麼成了unbound method了。
再看看從例項t中訪問hello
Python程式碼-
>>> t.hello -
<bound method T.hello of <main.T object at 0x00CD0E50>> -
>>>
是一個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程式碼-
class Descriptor(object): -
def get(self, obj, type=None): -
return ‘get’, self, obj, type -
def set(self, obj, val): -
print ‘set’, self, obj, val -
def delete(self, obj): -
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程式碼-
class T(object): -
d = Descriptor() -
t = T()
d是T的類屬性,作為Descriptor的例項,它有__get__等方法,顯然,d滿足了所有的條件,現在它就是一個descriptor!
Python程式碼-
>>> t.d #t.d,返回的實際是d.get(t, T) -
(’get’, <main.Descriptor object at 0x00CD9450>, <main.T object at 0x00CD0E50>, <class ’main.T’>) -
>>> T.d #T.d,返回的實際是d.get(None, T),所以obj的位置為None -
(’get’, <main.Descriptor object at 0x00CD9450>, None, <class ’main.T’>) -
>>> t.d = ’hello’ #在例項上對descriptor設定值。要注意的是,現在顯示不是返回值,而是set方法中 -
print語句輸出的。 -
set <main.Descriptor object at 0x00CD9450> <main.T object at 0x00CD0E50> hello -
>>> t.d #可見,呼叫了Python呼叫了set方法,並沒有改變t.d的值 -
(’get’, <main.Descriptor object at 0x00CD9450>, <main.T object at 0x00CD0E50>, <class ’main.T’>) -
>>> T.d = ’hello’ #沒有呼叫set方法 -
>>> T.d #確實改變了T.d的值 -
’hello’ -
>>> t.d #t.d的值也變了,這可以理解,按我們上面說的屬性查詢策略,t.d是從T.dict中得到的 -
T.dict[’d’]的值是‘hello’,t.d當然也是‘hello’ -
’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程式碼-
class Descriptor(object): -
def get(self, obj, type=None): -
return ‘get’, self, obj, type -
class T(object): -
d = Descriptor() -
t = T()
Python程式碼
-
>>> t.d -
(’get’, <main.Descriptor object at 0x00CD9550>, <main.T object at 0x00CD9510>, <class ’main.T’>) -
>>> t.d = ’hello’ -
>>> t.d -
’hello’ -
>>>
在例項上對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程式碼-
>>> t.dict -
{’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。
轉自:http://blog.csdn.net/huithe/article/details/7484606
但這個也非原創,也是轉載的,裡面並沒有標明原創地址。