python3完全使用了新式類,廢棄了舊式類,getattribute作為新式類的一個特性有非常奇妙的作用。檢視一些部落格和文章後,發現想要徹底理解getattr和getattribute的區別,實際上需要理解python中屬性的查詢順序、描述器(descriptor)、__get__、__set__、__dict__等知識。
首先需要指出,如果一個類只定義了__get__方法則稱之為non-data descriptor(非資料描述器),如果這個類將__get__和 __set__都定義了,則稱之為data descriptor(資料描述器),具體可見我的另一篇部落格,方便理解。下面介紹例項屬性的查詢順序。假設 t = T(),t.at的查詢順序如下:
1、如果是python自動產生的屬性,返回,否則進行第2步
2、如果‘at’出現在了T或者其父類和祖先類的__dict__
中(即‘at’是一個類屬性,並非只屬於t的例項屬性),並且at是一個data descriptor,則優先呼叫其__get__
方法。不是data descriptor或者沒有該屬性則進行第2步。
3、查詢例項t的__dict__
中是否有at屬性,有則返回,沒有則到第3步。
4、查詢t的父類和祖先類的__dict__
中是否有at屬性,如果沒有則執行第4步,如果有則執行如下步驟:
4.1 at是一個non-data descriptor,呼叫其__get__
方法,不是則執行3.2
4.2 返回__dict__[`at`]
5、如果例項t的父類中有__getattr__
方法,則呼叫該方法,沒有則丟擲AttributeError。
注意每次類或例項呼叫屬性時getattribute會被無條件首先呼叫。下方程式碼略長,耐心檢視。
class Descriptor: # 定義描述器的類
def __get__(self, instance, owner): # get方法用於返回例項的a屬性
print(`3 get called,`, `instance is`, instance, `,owner is`, owner)
return instance.a
def __set__(self, instance, value): # set方法用於修改例項的a屬性
print(`4 set called,`, `instance is`, instance, `,value is`, value)
instance.a = value**2
def __getattribute__(self, item):
print(`5 Des getattribute called, item is %s` % item)
class NotDescriptor: # 定義non-data descriptor
def __get__(self, instance, owner):
print(`6 get called,`, `instance is`, instance, `,owner is`, owner)
return instance.a - 100
class T:
desc = Descriptor() # 類屬性,一個資料描述器 data descriptor
Not_Desc = NotDescriptor() # 類屬性,一個非資料描述器 non-data descriptor
def __init__(self):
self.a = 123
self.not_desc = `instance not_desc`
def func(self):
return `T func`
def __getattribute__(self, item):
print(`1 getattribute called, item is `, item)
return object.__getattribute__(self, item)
def __getattr__(self, item):
print(`2 getattr called, item is`, item)
raise AttributeError(`NO such attr %s` % item)
以下的測試均使用上方的程式碼,每次的輸出操作都重置過,以避免混淆。
1 getattribute called, item is func
T func # 例項的函式
1 getattribute called, item is a
123 # 例項的屬性
==================================================
4 set called, instance is <__main__.T object at 0x000001E55B630240> ,value is 2
# set方法前不呼叫getattribute。instance是擁有該描述器類的一個例項。value是要設定的值。
==================================================
1 getattribute called, item is desc
3 get called, instance is <__main__.T object at 0x000001E55B630240> ,owner is <class `__main__.T`>
# get方法前還是優先呼叫getattribute,instance是擁有該描述器物件的一個例項。owner是擁有者本身
1 getattribute called, item is a
t.a = 4 # 2**2 = 4
可見呼叫例項的屬性和方法時都會無條件首先呼叫getattrtibute方法,而set方法則不會呼叫。另外程式碼利用描述器的get和set方法,實際上已經實現了類似於@property的使用(當然,property裝飾器其實就是基於描述器實現的,對property裝飾器不瞭解的話可以檢視我的另一篇部落格)。
下面繼續使用上方程式碼驗證__getattr__
、非描述器的執行順序。
t = T()
print(t.not_desc)
--------結果如下------
1 getattribute called, item is not_desc
instance not_desc
回想前面提到的屬性查詢順序,‘not_desc’在類的__dict__
被找到了,但不是descriptor,所以執行第2步,在例項的__dict__
中發現該屬性,返回。
t = T()
print(t.Not_Desc)
--------結果如下------
1 getattribute called, item is Not_Desc
6 get called, instance is <__main__.T object at 0x0000017F70A006A0> ,owner is <class `__main__.T`>
# 這裡是NonDataDescriptor中的get方法
1 getattribute called, item is a
23 # a的初始值為123 由get方法返回的是123-100=23
根據我們之前提到的順序,‘Not_Desc’屬性在類的__dict__
中找到但不是data descriptor,又沒有在例項的dict中找到,所以進行到了4.1步驟,發現它是一個non-data descriptor,然後就執行了其中的get方法。
t = T()
print(t.bbbbbb) # T類中沒有定義該屬性
------結果如下----------
1 getattribute called, item is bbbbbb
2 getattr called, item is bbbbbb
Traceback (most recent call last):
File "C:/省略/.py", line 40, in <module>
print(t.bbbbbb)
File "C:C:/省略/.py.py", line 36, in __getattr__
raise AttributeError(`NO such attr %s` % item)
AttributeError: NO such attr bbbbbb
可見,當我們訪問一個沒有被定義的屬性時,仍然會首先呼叫getattribute,根據屬性查詢原則,在例項和類中都沒有找到這個屬性,於是執行getattr。到這裡我們也就瞭解到了python中屬性的查詢順序、getattribute和getattr的執行順序。
回過頭來,getattribute會在呼叫類和例項的屬性時無條件呼叫,所以可以用於許可權鑑別、日誌記錄等操作;而getattr會在屬性沒有被找到的時候執行,因此可以用來做一些兜底的操作,可見這篇部落格
另外注意重寫getattribute時的迴圈陷阱,返回語句要寫成return object.__getattribute__()
,不要使用return self.xxx
,否則self相當於又指向了自己,而getattribute會無條件呼叫,從而進入無限迴圈。
最後的我們檢視類和例項的__dict__屬性,看看又什麼不同
t = T()
print(t.__dict__)
print(T.__dict__)
------結果如下--------
1 getattribute called, item is __dict__
{`a`: 123, `not_desc`: `instance not_desc`}
{`__module__`: `__main__`, `desc`: <__main__.Descriptor object at 0x0000021FD20C0128>, `Not_Desc`: <__main__.NotDescriptor object at 0x0000021FD20C0160>, `__init__`: <function T.__init__ at 0x0000021FD20B59D8>, `func`: <function T.func at 0x0000021FD20B5A60>, `__getattribute__`: <function T.__getattribute__ at 0x0000021FD20B5AE8>, `__getattr__`: <function T.__getattr__ at 0x0000021FD20B5B70>, `__dict__`: <attribute `__dict__` of `T` objects>, `__weakref__`: <attribute `__weakref__` of `T` objects>, `__doc__`: None}
可見例項t只有init中定義的屬性,而類T中才有描述器和非描述器等屬性。
參考:
https://blog.csdn.net/yitiaodashu/article/details/78974596
https://www.cnblogs.com/Vito2008/p/5280216.html
https://www.cnblogs.com/pyxiaomangshe/p/7927540.html