property的AttributeError的传播问题
/ / / 阅读数:11063前言
去年我写过一篇 你用对 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__
- 使用 property 装饰器的方法的逻辑中有未预期的属性调用
相当于方法中的 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' |
就是这样~