python的提供一系列和屬性訪問有關的特殊方法:__get__
, __getattr__
, __getattribute__
, __getitem__
。本文闡述它們的區別和用法。html
通常狀況下,屬性訪問的默認行爲是從對象的字典中獲取,並當獲取不到時會沿着必定的查找鏈進行查找。例如 a.x
的查找鏈就是,從 a.__dict__['x']
,而後是 type(a).__dict__['x']
,再經過 type(a)
的基類開始查找。python
若查找鏈都獲取不到屬性,則拋出 AttributeError
異常。性能
__getattr__
方法這個方法是當對象的屬性不存在是調用。若是經過正常的機制能找到對象屬性的話,不會調用 __getattr__
方法。code
class A: a = 1 def __getattr__(self, item): print('__getattr__ call') return item t = A() print(t.a) print(t.b) # output 1 __getattr__ call b
__getattribute__
方法這個方法會被無條件調用。無論屬性存不存在。若是類中還定義了 __getattr__
,則不會調用 __getattr__()
方法,除非在 __getattribute__
方法中顯示調用__getattr__()
或者拋出了 AttributeError
。htm
class A: a = 1 def __getattribute__(self, item): print('__getattribute__ call') raise AttributeError def __getattr__(self, item): print('__getattr__ call') return item t = A() print(t.a) print(t.b)
因此通常狀況下,爲了保留 __getattr__
的做用,__getattribute__()
方法中通常返回父類的同名方法:對象
def __getattribute__(self, item): return object.__getattribute__(self, item)
使用基類的方法來獲取屬性能避免在方法中出現無限遞歸的狀況。繼承
__get__
方法這個方法比較簡單說明,它與前面的關係不大。遞歸
若是一個類中定義了 __get__()
, __set__()
或 __delete__()
中的任何方法。則這個類的對象稱爲描述符。文檔
class Descri(object): def __get__(self, obj, type=None): print("call get") def __set__(self, obj, value): print("call set") class A(object): x = Descri() a = A() a.__dict__['x'] = 1 # 不會調用 __get__ a.x # 調用 __get__
若是查找的屬性是在描述符對象中,則這個描述符會覆蓋上文說的屬性訪問機制,體如今查找鏈的不一樣,而這個行文也會由於調用的不一樣而稍有不同:get
a.x
則轉換爲調用: 。type(a).__dict__['x'].__get__(a, type(a))
A.x
則轉換爲:A.__dict__['x'].__get__(None, A)
__getitem__
方法這個調用也屬於無條件調用,這點與 __getattribute__
一致。區別在於 __getitem__
讓類實例容許 []
運算,能夠這樣理解:
__getattribute__
適用於全部 .
運算符;__getitem__
適用於全部 []
運算符。class A(object): a = 1 def __getitem__(self, item): print('__getitem__ call') return item t = A() print(t['a']) print(t['b'])
若是僅僅想要對象可以經過 []
獲取對象屬性能夠簡單的:
def __getitem(self, item): return object.__getattribute__(self, item)
當這幾個方法同時出現可能就會擾亂你了。我在網上看到一份示例還不錯,稍微改了下:
class C(object): a = 'abc' def __getattribute__(self, *args, **kwargs): print("__getattribute__() is called") return object.__getattribute__(self, *args, **kwargs) # return "haha" def __getattr__(self, name): print("__getattr__() is called ") return name + " from getattr" def __get__(self, instance, owner): print("__get__() is called", instance, owner) return self def __getitem__(self, item): print('__getitem__ call') return object.__getattribute__(self, item) def foo(self, x): print(x) class C2(object): d = C() if __name__ == '__main__': c = C() c2 = C2() print(c.a) print(c.zzzzzzzz) c2.d print(c2.d.a) print(c['a'])
能夠結合輸出慢慢理解,這裏還沒涉及繼承關係呢。總之,每一個以 __ get
爲前綴的方法都是獲取對象內部數據的鉤子,名稱不同,用途也存在較大的差別,只有在實踐中理解它們,才能真正掌握它們的用法。