Python的__getattribute__二三事


本來以為自己對__getattribute__已經比較了解了,結果用到的時候,才發現有一些知識點之前一直沒有真正弄明白,記錄如下(針對python3,python2差異較大):

  1. 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'
  1. 只有在實例的命名空間查找屬性的時候,才會觸發__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__的知識點就這些了,以上均為個人原創,平時學習的積累,打字不易,轉載還請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM