簡述
結(jié)合代碼區(qū)分一下python的幾個魔術(shù)方法__get__, __getattr__, __getattribute__, __getitem__
操作環(huán)境
win7_64 + Python 2.7.16 + IPython 5.9.0
__dict__
首先我們通過obj.attr的方式訪問對象屬性的時候,在python內(nèi)部,實際是訪問了obj.__dict__[attr],__dict__有文章單獨說明,這里只需要簡單理解,一般情況下,我們訪問一個對象的屬性,其實是在讀取這個屬性的__dict__字典:
In [7]: class Test(object):
...: def __init__(self):
...: self.a = 1
...: self.b = 2
...:
# 定義了一個Test類的對象t
In [8]: t = Test()
# 雖然t沒有顯示定義__dict__屬性,但是python會自動生成,它是一個字典
In [9]: t.__dict__
Out[9]: {'a': 1, 'b': 2}
In [10]: t.a
Out[10]: 1
# t.a的訪問方式等價于t.__dict__['a']的訪問方式
In [11]: t.__dict__['a']
Out[11]: 1
__getattr__
如果一個類定義了此方法,當(dāng)訪問一個類不存在的屬性的時候,會訪問此方法并且返回對應(yīng)的值,如果不存在對應(yīng)的訪問,應(yīng)該拋出AttributeError
In [10]: class Test(object):
...: def __init__(self):
...: self.a = 1
...:
...: def __getattr__(self, name): # name參數(shù)就是訪問的屬性
...: print name
...: return 2
...:
...: t = Test()
...:
In [11]: t.a
Out[11]: 1
In [12]: t.b
b
Out[12]: 2
In [13]: t.c
c
Out[13]: 2
t對象有a屬性,所以獲取到了對應(yīng)的值,但是b和c均不是對象t的屬性,故訪問了__getattr__方法,參數(shù)name獲取到了對應(yīng)的屬性名,并且都返回2.
注意下面的代碼
class Test(object):
def __init__(self):
self.a = 1
def __getattr__(self, name):
return self.name
t = Test()
t.b
當(dāng)我們在訪問t.b的時候,t對象沒有b屬性,會訪問__getattr__,此方法再次通過self.name的方式嘗試訪問b屬性,還是不存在,同樣走到了__getattr__,這樣會造成無限循環(huán),直到最后拋出RuntimeError: maximum recursion depth exceeded異常,我們可以通過下面的方式來避免出現(xiàn)這種情況:
class Test(object):
def __init__(self):
self.a = 1
def __getattr__(self, name):
# 有對應(yīng)的屬性,我們才訪問它的屬性
if hasattr(self, name):
# getattr訪問屬性的方式和t.b的方式是一樣的
return getattr(self, name)
else:
# 否則直接拋出異常
raise AttributeError()
t = Test()
t.b
__getattribute__
只要定義了此函數(shù),任何屬性訪問,會無條件的調(diào)用此方法,也就是屏蔽了__dict__和__getattr__
In [10]: class Test(object):
...: def __init__(self):
...: self.a = 1
...:
...: def __getattr__(self, name):
...: print '__getattr__'
...:
...: def __getattribute__(self, name):
...: print '__getattribute__'
...:
...: t = Test()
...: t.b
...:
__getattribute__
# 同樣t.__dict__也是在訪問t的屬性,也訪問到了__getattribute__方法
In [11]: t.__dict__
__getattribute__
- 它和
__getattr__一樣,如果不想有對應(yīng)的屬性,應(yīng)該拋出AttributeError,而且也需要注意遞歸訪問的情況出現(xiàn)- 此方法只針對新式類有效,老式類此方法是不會生效的,除非主動調(diào)用
__getitem__
這個方法最簡單,定義此方法的類一般是模擬容器的行為,比如模擬字典,列表的訪問
In [12]: class Test(object):
...: def __init__(self):
...: self.a = 1
...:
...: def __getitem__(self, key):
...: print key
...:
...: t = Test()
...: t['a']
...: t[1]
...:
a
1
- 其中key在列表里面是整數(shù),在字典里面可以是字符串
- 如果key的類型不正確,應(yīng)該拋出TypeError
- 如果key索引錯誤拋出IndexError
- 如果key在字典不存在則拋出KeyError
__get__
此方法涉及到描述符的概念,由于篇幅較長,會單獨寫一篇文章記錄。
總結(jié)
- 新式類只要定義了
__getattribute__,則會無條件訪問,但老式類不生效 - 定義了
__getattr__,如果訪問的屬性不存在的時候,才會訪問此方法 -
__getitem__最簡單,用于模擬容器的訪問方式 -
__getattribute__和__getattr__都應(yīng)避免進(jìn)入遞歸調(diào)用,并且如果訪問的屬性不應(yīng)該存在的情況下,需要拋出AttributeError異常 -
getattr(obj, attr)的方式訪問屬性和obj.attr的方式是一樣的,都等同于obj.__dict__[attr]