property的AttributeError的傳播問題
前言
去年我寫過一篇 你用對 hasattr 了嘛? 介紹過被 property 裝飾的方法內部拋錯會引起 hasattr 的結果爲 False。
今天又遇到了一個 AttributeError 向上傳播的問題 (Python 2),一起來看看
問題
先上代碼:
In : class T(object): ...: @property ...: def name(self): ...: print(self.missing_attribute) ...: return 42 ...: def __getattr__(self, name): ...: raise AttributeError(name) ...: In : t = T() In : t.name --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-6-afc812d51b9a> in <module>() ----> 1 t.name <ipython-input-4-a0ba2d3a6446> in __getattr__(self, name) 5 return 42 6 def __getattr__(self, name): ----> 7 raise AttributeError(name) 8 AttributeError: name In : [k for k in dir(t) if not k.startswith('_')] Out: ['name']
這個例子需要細品,明明有 name 這個屬性卻拋出了 AttributeError。分析一下:
-
通過
__getattr__
定製屬性查找的邏輯,當用戶試圖獲取一個不存在的屬性時就會拋 AttributeError 錯誤 -
獲取 name 屬性的邏輯中包含
print (self.missing_attribute)
這句,由於沒有這個屬性會執行到__getattr__
然後拋 AttributeError
一看到這個違背常理的錯誤,我就想起來 hasattr 的那個問題,覺得應該是類似的原因。網上一搜果然之前有人已經給 CPython 提過 issue 了,具體可以看延伸閱讀鏈接。總結一下,這個問題的出現需要有 2 個點都滿足:
__getattr__
相當於方法中的 AttributeError 沒有被處理,傳播到 __getattr__
了。再看一個例子:
In : class T(object): ...: @property ...: def name(self): ...: raise AttributeError('This message will not be displayed!') ...: return 'Hello' ...: def __getattr__(self, name): ...: return 0 ...: In : t = T() In : t.name Out: 0
一旦 name 方法中由於各種原因會拋 AttributeError 錯誤 (別的錯誤不行),就會走 __getattr__
裏面的邏輯。
怎麼解決?
這不是一個 BUG,但是代碼編寫者不能規避這類問題,怎麼辦呢?最簡單的辦法是使用 __getattribute__
替代 __getattr__
,演示一下:
In : class T(object): ...: @property ...: def name(self): ...: raise AttributeError('This message will not be displayed!') ...: return 'Hello' ...: ...: def __getattribute__(self, name): ...: try: ...: return super(T, self).__getattribute__(name) ...: except AttributeError as e: ...: raise e ...: In : t = T() In : t.name --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-28-afc812d51b9a> in <module>() ----> 1 t.name <ipython-input-26-a27dd1330f44> in __getattribute__(self, name) 9 return super(T, self).__getattribute__(name) 10 except AttributeError as e: ---> 11 raise e 12 AttributeError: This message will not be displayed! In : class T(object): ...: @property ...: def name(self): ...: print(self.missing_attribute) ...: return 42 ...: def __getattribute__(self, name): ...: try: ...: return super(T, self).__getattribute__(name) ...: except AttributeError as e: ...: raise e ...: In : t = T() In : t.name --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-31-afc812d51b9a> in <module>() ----> 1 t.name <ipython-input-29-da47b1a1e093> in __getattribute__(self, name) 8 return super(T, self).__getattribute__(name) 9 except AttributeError as e: ---> 10 raise e 11 AttributeError: 'T' object has no attribute 'missing_attribute'
就是這樣~