本來以為自己對__getattribute__已經比較了解了,結果用到的時候,才發現有一些知識點之前一直沒有真正弄明白,記錄如下(針對python3,python2差異較大):
- object類有__getattribute__屬性,因此所有的類默認就有__getattribute__屬性(所有類都繼承自object),object的__getattribute__起什么用呢?它做的就是查找自定義類的屬性,如果屬性存在則返回屬性的值,如果不存在則拋出AttributeError。
還是看代碼:
>>> class A:
def __getattribute__(self, name):
print('in A')
return super().__getattribute__(name)
>>> class B(A):
def spam(self):
print('spam')
>>> b = B()
>>> in A
b.spam()
in A
spam
當在命令行輸入b.spam(,甚至括號還沒有輸入完整的時候,就已經觸發了__getattribute__。可見,當實例在查找任何屬性的時候(注意是實例,這是另一個知識點,后面再詳談),都會觸發__getattribute__方法。
基於此特性,可以方便的使用類裝飾器為其所有屬性(不管是有的還是沒有的)擴充功能,例如,加一個log記錄功能,代碼如下:
>>> def log_attr(cls):
cls_getattribute = cls.__getattribute__
def new_getattribute(self, name):
print('catch name')
return cls_getattribute(self, name)
cls.__getattribute__ = new_getattribute
return cls
>>> @log_attr
class C:
def __init__(self, x):
self.x = x
def spam(self):
print('spam')
>>> c = C(42)
>>> c.x
catch name
42
>>> catch name
c.spam()
catch name
spam
>>> c.y
catch name
Traceback (most recent call last):
File "<pyshell#83>", line 1, in <module>
c.y
File "<pyshell#77>", line 5, in new_getattribute
return cls_getattribute(self, name)
AttributeError: 'C' object has no attribute 'y'
- 只有在實例的命名空間查找屬性的時候,才會觸發__getattribute__,在類的命名空間中查找,是不會觸發__getattribute__的。
還是第一個例子:
>>> B.spam
<function B.spam at 0x000001D3759646A8>
>>> b = B()
>>> B.spam(b)
spam
可見,當類直接調用spam方法的時候,不會觸發__getattribute__,而且當以B.spam(b)形式調用的時候,巧妙的繞過了__getattribute__。
接下來,是一個重要的知識點,即特殊的方法,如__len__,__str__等等操作符重載方法,當隱式調用的時候,在python3中會直接在類的命名空間中查找,因此是不會觸發__getattribute__的!!如下:
>>> class C:
def __len__(self):
return 42
def __getattribute__(self, name):
print(f'catch {name}')
return super().__getattribute__(name)
>>> c = C()
>>> len(c)
42
but,當直接顯示調用的時候,python不會將其當作啥特殊方法,仍然會從實例的命名空間查找,此時,就會觸發__getattribute__:
>>> c.__len__()
catch __len__
42
最大的影響就是,在委托類型的代碼中,操作符重載的方法代碼得重新寫,如下:
>>> class A:
def __len__(self):
return 42
def attr1(self):
print('attr1')
>>> class B:
def __init__(self):
self.a = A()
def __getattribute__(self, name):
if name == 'a':
return super().__getattribute__(name)
else:
return getattr(self.a, name)
>>> b.attr1()
attr1
>>> len(b)
Traceback (most recent call last):
File "<pyshell#170>", line 1, in <module>
len(b)
TypeError: object of type 'B' has no len()
可見,attr1正確的被代理,但是len方法沒有被代理,因為隱式調用的時候,直接在B類的命名空間查找__len__方法,根本就沒有觸發__getattribute__。如果顯示調用,就可以被代理了。
>>> b.__len__()
42
最后,還有一個坑需要注意,如上例B類的__getattribute__方法中,判斷一下a是否B自身的實例屬性的代碼不可少,是則調用父類的__getattribute__方法返回正確的self.a,否則在getattr(self.a, name)中,self.a會不斷的觸發__getattribute__,從而會陷入無限循環。
對了,最后還有一小點,有一個比較特殊的操作符重載方法,即dir,因為它會從實例的命名空間開始查找__dict__和__class__特殊方法,因此也會觸發__getattribute__。
貌似關於__getattribute__的知識點就這些了,以上均為個人原創,平時學習的積累,打字不易,轉載還請注明出處。