在Python中有這兩個魔法方法容易讓人混淆:__getattr__和getattribute。通常我們會定義__getattr__而從來不會定義getattribute,下面我們來看看這兩個的區別。
__getattr__魔法方法
class MyClass:
def __init__(self, x):
self.x = x
def __getattr__(self, item):
print('{}屬性為找到!'.format(item))
return None
>>> obj = MyClass(1)
>>> obj.x
1
>>> obj.y
y屬性為找到!
None
我們定義一個MyClass
類,設定一個例項屬性為x,值為1。obj
為這個類的例項,獲取obj.x
返回1,而獲取obj.y
發現屬性找不到,原因是obj的例項變數中不包含y,找不到某屬性時會呼叫__getattr__
方法。
**呼叫__getattr__詳細過程如下:**
obj.attr
- 首先會在物件的例項屬性中尋找,找不到執行第二步
- 來到物件所在的類中查詢類屬性,如果還找不到執行第三步
- 來到物件的繼承鏈上尋找,如果還找不到執行第四步
- 呼叫
obj.__getattr__
方法,如果使用者沒有定義或者還是找不到,丟擲AttributeError
異常,屬性查詢失敗!
class MyClass:
def __init__(self, x):
self.x = x
>>> obj = MyClass(1)
>>> obj.y
AttributeError: 'MyClass' object has no attribute 'a'
如上程式碼,沒有定義__getattr__魔法方法,又找不到屬性,就會丟擲異常
__getattribute__魔法方法
當我們呼叫物件的屬性時,首先會呼叫__getattribute__
魔法方法。
obj.x
obj.__getattribute__(x)
如上程式碼,這兩個程式碼其實是等價的。當__getattribute__
查詢失敗,就會去呼叫__getattr__
方法。
程式碼演示
class MyClass:
def __init__(self, x):
self.x = x
def __getattribute__(self, item):
print('正在獲取屬性{}'.format(item))
return super(MyClass, self).__getattribute__(item)
>>> obj = MyClass(2)
>>> obj.x
正在獲取屬性x
2
我們使用__getattribute__
魔法方法時,要返回父類的方法,不然很難寫對
下面程式碼是一個陷阱,會產生無限遞迴
class MyClass:
def __init__(self, x):
self.x = x
def __getattribute__(self, item):
print('正在獲取屬性{}'.format(item))
return self.item
>>> obj = MyClass(2)
>>> obj.x
File "xxx", line 11, in __getattribute__
print('正在獲取屬性{}'.format(item))
RecursionError: maximum recursion depth exceeded while calling a Python object
上面的程式碼看起來似乎是對的,但卻調入了無限遞迴的陷阱,相當於
def __getattribute__(self, item):
print('正在獲取屬性{}'.format(item))
return self.__getattribute__(item)
要十分警惕。
另外,內建的getattr和hasattr也會觸發這個魔法方法
>>> getattr(obj, 'x', None)
正在獲取屬性x
2
>>> hasattr(obj, 'x', None)
正在獲取屬性x
True
其他細節需要注意
class MyClass:
x = 999
def __init__(self, x):
self.x = x
def __getattribute__(self, item):
print('正在獲取屬性{}'.format(item))
return super(MyClass, self).__getattribute__(item)
上面程式碼中,定義了一個類屬性x和一個例項屬性x,這兩個屬性同名,根據Python語法規則,當物件獲取屬性x的時候,首先會在例項屬性中尋找,如果找不到才回去類屬性中查詢
>>> obj = MyClass(2)
>>> print(obj.x)
正在獲取屬性x
2
>>> del obj.x #刪除了例項屬性x
>>> print(obj.x) #此時訪問的是類屬性
正在獲取屬性
999
這樣就能印證了上面所說__getattribute__
的查詢順序。通常該方法在框架中可能會用到,一般情況下無需使用。