我正在与
__getattr__
作斗争。我有一个复杂的递归代码库,其中传播异常非常重要。
class A(object):
@property
def a(self):
raise AttributeError('lala')
def __getattr__(self, name):
print('attr: ', name)
return 1
print(A().a)
结果:
('attr: ', 'a')
1
__getattr__
文档)。 getattr()
可以只使用A.__dict__
。有什么想法吗?
我刚刚将代码更改为
class A(object):
@property
def a(self):
print "trying property..."
raise AttributeError('lala')
def __getattr__(self, name):
print('attr: ', name)
return 1
print(A().a)
并且,正如我们所看到的,确实首先尝试了该属性。但由于它声称不在那里(通过提高
AttributeError
),__getattr__()
被称为“最后的手段”。
它没有明确记录,但可以算在“当属性查找在通常的地方没有找到属性时调用”。
在同一个类中使用
__getattr__
和属性是危险的,因为它可能会导致非常难以调试的错误。
如果属性的 getter 抛出
AttributeError
,则 AttributeError
会被默默捕获,并调用 __getattr__
。通常,这会导致 __getattr__
失败并出现异常,但如果您非常不幸,则不会,而且您甚至无法轻松地将问题追溯到 __getattr__
。
编辑:此问题的示例代码可以在这个答案中找到。
除非你的属性获取器很简单,否则你永远不能 100% 确定它不会抛出
AttributeError
。异常可能会抛出几个深度级别。
您可以这样做:
__getattr__
。try ... except
块AttributeError
@property
装饰器,它捕获 AttributeError
并将其重新抛出为 RuntimeError
。另请参阅 http://blog.devork.be/2011/06/using-getattr-and-property_17.html
编辑:如果有人正在考虑解决方案 4(我不推荐),可以这样做:
def property_(f):
def getter(*args, **kwargs):
try:
return f(*args, **kwargs)
except AttributeError as e:
raise RuntimeError, "Wrapped AttributeError: " + str(e), sys.exc_info()[2]
return property(getter)
然后在覆盖
@property_
的类中使用 @property
而不是 __getattr__
。
__getattribute__
文档说:
如果该类还定义了
,则不会调用后者,除非__getattr__()
显式调用它或引发__getattribute__()
。AttributeError
我读到这篇文章(由 inclusio unius est exclusio alterius)说属性访问 will 调用
__getattr__
如果 object.__getattribute__
(即“无条件调用来实现属性访问”)碰巧引发 AttributeError
- 无论是直接还是在描述符中 __get__
(例如属性 fget);请注意,__get__
应该“返回(计算的)属性值或引发AttributeError
异常”。
打个比方,运算符特殊方法可以引发
NotImplementedError
,然后将尝试其他运算符方法(例如 __radd__
表示 __add__
)。
__getattr__
。也许这就是您认为它“捕获”错误的原因。然而,事实并非如此,Python 的属性访问功能捕获了它们,然后调用 __getattr__
。
但是
__getattr__
本身并没有捕获任何错误。如果您在 __getattr__
中引发 AttributeError,您将获得无限递归。
经常遇到这个问题,因为我经常实现
__getattr__
并且有很多 @property
方法。这是我想出的一个装饰器,用于获取更有用的错误消息:
def replace_attribute_error_with_runtime_error(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
try:
return f(*args, **kwargs)
except AttributeError as e:
# logging.exception(e)
raise RuntimeError(
'{} failed with an AttributeError: {}'.format(f.__name__, e)
)
return wrapped
并像这样使用它:
class C(object):
def __getattr__(self, name):
...
@property
@replace_attribute_error_with_runtime_error
def complicated_property(self):
...
...
底层异常的错误消息将包含其实例引发底层
AttributeError
的类的名称。
如果您愿意,也可以记录它。
无论如何,当你将
@property
与 __getattr__
结合起来时,你就注定失败了:
class Paradise:
pass
class Earth:
@property
def life(self):
print('Checking for paradise (just for fun)')
return Paradise.breasts
def __getattr__(self, item):
print("sorry! {} does not exist in Earth".format(item))
earth = Earth()
try:
print('Life in earth: ' + str(earth.life))
except AttributeError as e:
print('Exception found!: ' + str(e))
给出以下输出:
Checking for paradise (just for fun)
sorry! life does not exist in Earth
Life in earth: None
当您真正的问题是打电话时
Paradise.breasts
。
当 __getattr__
上升时,总是会调用
AtributeError
。异常的内容将被忽略。
可悲的是,这个问题没有解决方案,因为
hasattr(earth, 'life')
将返回 True
(只是因为 __getattr__
已定义),但仍然会被属性 'life' 到达,因为它不存在,而真正的根本问题在于Paradise.breasts
。
我的部分解决方案涉及在
@property
块中使用 try-except,已知这些块会遇到 AttributeError
异常。
我知道自从提出这个问题以来已经有一段时间了,但这是我的解决方法:
class PropertyAttributeError(Exception):
pass
def getattr_safe_property(func):
"""The A class uses 'property', and overloads
:obj:`__getattr__`. This is not a good idea, because if an AttributeError is
raised during the evaluation of a property, then the error message will
look like the property itself is not found.
To avoid those misleading errors, all properties of A must be written as such:
.. code-block:: python
@property
@getattr_safe_property
def some_property(self):
...
Since the new :obj:`~PropertyAttributeError` is raised from the original
:obj:`AttributeError`, the error stack will contain the actual problematic
line, but it will not be silently caught by :obj:`__getattr__`.
"""
def wrapper(self):
try:
return func(self)
except AttributeError as error:
raise PropertyAttributeError(
"An AttributeError was raised while evaluating the property "
f"'{func.__name__}' of a {self.__class__.__name__} instance:"
f" {error}"
) from error
return wrapper
class A:
def __init__(self):
self._a = 0
@property
@getattr_safe_property
def some_property(self):
return {}
def __getattr__(self, item):
# Do some stuff
raise AttributeError(f"No attribute {item}")
@property
@getattr_safe_property
def a(self):
print(self.some_property.wrong_attribute)
return self._a
@a.setter
def a(self, value):
self._a = value
b = A()
b.a = 3
print(b.a)
然后您将得到正确的错误堆栈:
Traceback (most recent call last):
File "path/to/file.py", line 8, in wrapper
return func(self)
^^^^^^^^^^
File "path/to/file.py", line 34, in a
print(self.some_property.wrong_attribute)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'dict' object has no attribute 'wrong_attribute'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "path/to/file.py", line 44, in <module>
print(b.a)
^^^
File "/path/to/file.py", line 10, in wrapper
raise PropertyAttributeError(
PropertyAttributeError: An AttributeError was raised while evaluating the property 'a' of a A instance: 'dict' object has no attribute 'wrong_attribute'