在Python中,訪問一個屬性的優先順序順序按照如下順序:
- 類屬性
- 資料描述符
- 執行個體屬性
- 非資料描述符
- __getattr__()方法 這個方法的完整定義如下所示:
def __getattr(self,attr) :#attr是self的一個屬性名稱 pass;
先來闡述下什麼叫資料描述符。
資料描述符是指實現了__get__,__set__,__del__方法的類屬性(由於Python中,一切皆是對象,所以你不妨把所有的屬性也看成是對象)
PS:個人覺得這裡最好把資料描述符等效於定義了__get__,__set__,__del__三個方法的介面。
闡述下這三個方法:
__get__的標準定義是__get__(self,obj,type=None),它非常接近於JavaBean的get
第一個函數是調用它的執行個體,obj是指去訪問屬性所在的方法,最後一個type是一個選擇性參數,通常為None(這個有待於進一步的研究)
例如給定類X和執行個體x,調用x.foo,等效於調用:
type(x).__dict__["foo"].__get__(x,type(x))
調用X.foo,等效於調用:
type(x).__dict__["foo"].__get__(None,type(x))
第二個函數__set__的標準定義是__set__(self,obj,val),它非常接近於JavaBean的set方法,其中最後一個參數是要賦予的值
第三個函數__del__的標準定義是__del__(self,obj),它非常接近Java中Object的Finailize()方法,指Python在回收這個垃圾對象時所調用到的解構函式,只是這個函數永遠不會拋出異常。因為這個對象已經沒有引用指向它,拋出異常沒有任何意義。
接下來,我們來一一比較這些優先順序.
首先來看類屬性
class A(object): foo=1.3; print str(A.__dict__);
輸出:
{"__dict__": <attribute "__dict__" of "A" objects>, "__module__": "__main__", "foo": 1.3, "__weakref__": <attribute "__weakref__" of "A" objects>, "__doc__": None}
從可以看出foo屬性在類的__dict__屬性裡,所以這裡用A.foo可以直接找到。這裡我們先跨過資料描述符,直接來看執行個體屬性。
class A(object): foo=1.3;a=A();print a.foo;a.foo=15;print a.foo;
這裡a.foo先輸出1.3後輸出15,不是說類屬性的優先順序比執行個體屬性的優先順序高嗎?按理a.foo應該不變才對?其實,這裡只是一個假象,真正的原因在於這裡將a.foo這個引用對象,不妨將其理解為可以指向任意資料類型的指標,指向了15這個int對象。
不信,可以繼續看:
class A(object): foo=1.3;a=A();print a.foo;a.foo=15;print a.foo;del a.foo;print a.foo;
這次在輸出1.3,15後最後一次又一次的輸出了1.3,原因在於a.foo最後一次又按照優先順序順序直接找到了類屬性A.foo
然後我們來看下資料描述符這一全新的語言概念。按照之前的定義,一個實現了__get__,__set__,__del__的類都統稱為資料描述符。我們來看下一個簡單的例子。
class simpleDescriptor(object): def __get__(self,obj,type=None) : pass; def __set__(self,obj,val): pass; def __del__(self,obj): pass class A(object): foo=simpleDescriptor();print str(A.__dict__);print A.foo;a=A();print a.foo;a.foo=13;print a.foo;
這裡get,set,del方法體內容都略過,雖然簡單,但也不失為一個資料描述符。讓我們來看下它的輸出:
{"__dict__": <attribute "__dict__" of "A" objects >, "__module__": "__main__", "foo": <__main__.simpleDescriptor object at 0x00C46930 >, "__weakref__": <attribute "__weakref__" of "A" objects >, "__doc__": None}NoneNoneNone
從可以看出,儘管我們對a.foo賦值了,但其依然為None,原因就在於__get__方法什麼都不返回。
為了更進一步的加深對資料描述符的理解,我們簡單的作下改造。
class simpleDescriptor(object): def __init__(self): self.result=None; def __get__(self,obj,type=None) : return self.result-10; def __set__(self,obj,val): self.result=val+3; print self.result; def __del__(self,obj): pass class A(object): foo=simpleDescriptor();a=A();a.foo=13;print a.foo;
列印的輸出結果為:
16 6
第一個16為我們在對a.foo賦值的時候,人為的將13加上3後作為foo的值,第二個6是我們在返回a.foo之前人為的將它減去了10。
所以我們可以猜測,常規的Python類在定義get,set方法的時候,如果無特殊需求,直接給對應的屬性賦值或直接返回該屬性值。如果自己定義類,並且繼承object類的話,這幾個方法都不用定義。
下面我們來看下執行個體屬性和非資料描述符。
class B(object): foo=1.3;b=B();print b.__dict__#print b.bar;b.bar=13;print b.__dict__print b.bar;
輸出結果為:
{}
{"bar": 13}
13
可見這裡在執行個體b.__dict__裡找到了bar屬性,所以這次可以擷取13了。
那麼什麼是非資料描述符呢?簡單的說,就是沒有實現get,set,del三個方法的所有類
讓我們任意看一個函數的描述:
def hello(): passprint dir(hello)
輸出:
["__call__", "__class__", "__delattr__", "__dict__", "__doc__", "__get__", "__getattribute__", "__hash__", "__init__", "__module__", "__name__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__setattr__", "__str__", "func_closure", "func_code", "func_defaults", "func_dict", "func_doc", "func_globals", "func_name"]
從上面可以看出所有的函數都有get方法,但都沒有set和del方法,所以所有的類成員函數都是非資料描述符。
看一個簡單的例子:
class simpleDescriptor(object): def __get__(self,obj,type=None) : return "get",self,obj,type;class D(object): foo=simpleDescriptor();d=D();print d.foo;d.foo=15;print d.foo;
輸出:
("get", <__main__.simpleDescriptor object at 0x00C46870 >, <__main__.D object at 0x00C46890 >, <class "__main__.D" >)15
可以看出執行個體屬性掩蓋了非資料描述符。
最後看下__getatrr__方法。它的標準定義是:__getattr__(self,attr),其中attr是屬性名稱
讓我們來看一個簡單的例子:
class D(object): def __getattr__(self,attr): return attr; #return self.attr; d=D();print d.foo,type(d.foo);d.foo=15;print d.foo;
輸出:
foo <type "str" > 15
可以看的出來Python在實在找不到方法的時候,就會求助於__getattr__方法。這有點像javascript中FF的私人實現__noSuchMethod__,或ruby中的method_missing.
注意這裡要避免無意識的遞迴,稍微改動下:
class D(object): def __getattr__(self,attr): #return attr; return self.attr; d=D();print d.foo,type(d.foo);d.foo=15;print d.foo;
這次會直接拋出堆疊溢位的異常,就像下面這樣:
RuntimeError: maximum recursion depth exceeded