__getattr__
__getattr__在當前主流的Python版本中都可用,重載__getattr__方法對類及其實例未定義的屬性有效。也就屬性是說,如果訪問的屬性存在,就不會調用__getattr__方法。這個屬性的存在,包括類屬性和實例屬性。
Python官方文檔的定義
Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for
self).nameis the attribute name.
class ClassA: x = 'a' def __init__(self): self.y = 'b' def __getattr__(self, item): return '__getattr__' if __name__ == '__main__': a = ClassA() # 輸出結果 a print(a.x) # 使用實例直接訪問實例存在的實例屬性時,不會調用__getattr__方法 # 輸出結果 b print(a.y) # 使用實例直接訪問實例不存在的實例屬性時,會調用__getattr__方法 # 輸出結果 __getattr__ print(a.z)
__getattribute__
__getattribute__僅在新式類中可用,重載__getattrbute__方法對類實例的每個屬性訪問都有效。
Python官方文檔的定義
Called unconditionally to implement attribute accesses for instances of the class.
示例代碼:
class ClassA: x = 'a' def __init__(self): self.y = 'b' def __getattribute__(self, item): return '__getattribute__' if __name__ == '__main__': a = ClassA() # 使用實例直接訪問存在的類屬性時,會調用__getattribute__方法 # 輸出結果 __getattribute__ print(a.x) # 使用實例直接訪問實例存在的實例屬性時,會調用__getattribute__方法 # 輸出結果 __getattribute__ print(a.y) # 使用實例直接訪問實例不存在的實例屬性時,也會調用__getattribute__方法 # 輸出結果 __getattribute__ print(a.z)
運行結果:
__getattribute__ __getattribute__ __getattribute__
另外,當同時定義__getattribute__和__getattr__時,__getattr__方法不會再被調用,除非顯示調用__getattr__方法或引發AttributeError異常。
示例代碼(__getattr__方法不會再被調用):
class ClassA: def __getattr__(self, item): print('__getattr__') def __getattribute__(self, item): print('__getatttribute__') if __name__ == '__main__': a = ClassA() a.x
運行結果:
__getatttribute__
由於__getattr__只針對未定義屬性的調用,所以它可以在自己的代碼中自由地獲取其他屬性,而__getattribute__針對所有的屬性運行,因此要十分注意避免在訪問其他屬性時,再次調用自身的遞歸循環。
當在__getattribute__代碼塊中,再次執行屬性的獲取操作時,會再次觸發__getattribute__方法的調用,代碼將會陷入無限遞歸,直到Python遞歸深度限制(重載__setter__方法也會有這個問題)。
示例代碼(無限遞歸):
class ClassA: x = 'a' def __getattribute__(self, item): print('__getattribute__') return self.item if __name__ == '__main__': a = ClassA() a.x
運行結果引發異常,提示達到最大遞歸深度
ecursionError: maximum recursion depth exceeded
同時,也沒辦法通過從__dict__取值的方式來避免無限遞歸
class ClassA: x = 'a' def __getattribute__(self, name): return self.__dict__[name] if __name__ == '__main__': a = ClassA() # 無限遞歸 a.x
為了避免無限遞歸,應該把獲取屬性的方法指向一個更高的超類,例如object(因為__getattribute__只在新式類中可用,而新式類所有的類都顯式或隱式地繼承自object,所以對於新式類來說,object是所有新式類的超類)。
修改代碼(避免無限遞歸循環):
class ClassA: x = 'a' def __getattribute__(self, item): print('__getattribute__') return super().__getattribute__(self, item) if __name__ == '__main__': a = ClassA() print(a.x)
運行結果正常:
__getattribute__ a
參考資料:
https://docs.python.org/3/reference/datamodel.html
