[英]Python: Why is __getattr__ catching AttributeErrors?
I'm struggling with __getattr__
. 我正在努力与__getattr__
。 I have a complex recursive codebase, where it is important to let exceptions propagate. 我有一个复杂的递归代码库,让异常传播很重要。
class A(object):
@property
def a(self):
raise AttributeError('lala')
def __getattr__(self, name):
print('attr: ', name)
return 1
print(A().a)
Results in: 结果是:
('attr: ', 'a')
1
Why this behaviour? 为什么会这样? Why is no exception thrown? 为什么没有例外? This behaviour is not documented ( __getattr__
documentation ). 此行为未记录( __getattr__
文档 )。 getattr()
could just use A.__dict__
. getattr()
只能使用A.__dict__
。 Any thoughts? 有什么想法吗?
I just changed the code to 我只是将代码更改为
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)
and, as we see, indeed the property is tried first. 而且,正如我们所见,确实首先尝试了财产。 But as it claims not to be there (by raising AttributeError
), __getattr__()
is called as "last resort". 但是因为它声称不存在(通过引发AttributeError
), __getattr__()
被称为“最后的手段”。
It is not documented clearly, but can maybe be counted under "Called when an attribute lookup has not found the attribute in the usual places". 它没有明确记录,但可以计入“当属性查找没有在通常的位置找到属性时调用”。
Using __getattr__
and properties in the same class is dangerous, because it can lead to errors that are very difficult to debug. 在同一个类中使用__getattr__
和属性是危险的,因为它可能导致很难调试的错误。
If the getter of a property throws AttributeError
, then the AttributeError
is silently caught, and __getattr__
is called. 如果属性的getter抛出AttributeError
,则会以静默方式捕获AttributeError
,并调用__getattr__
。 Usually, this causes __getattr__
to fail with an exception, but if you are extremely unlucky, it doesn't, and you won't even be able to easily trace the problem back to __getattr__
. 通常,这会导致__getattr__
失败并出现异常,但如果您非常不走运,则不会,并且您甚至无法轻松将问题追溯回__getattr__
。
Unless your property getter is trivial, you can never be 100% sure it won't throw AttributeError
. 除非你的属性getter是微不足道的,否则你永远不能100%确定它不会抛出AttributeError
。 The exception may be thrown several levels deep. 异常可能会被抛出几个层次。
Here is what you could do: 这是你可以做的:
__getattr__
in the same class. 避免在同一个类中使用属性和__getattr__
。 try ... except
block to all property getters that are not trivial 将try ... except
块添加到所有非平凡的属性getter中 AttributeError
保持属性getter简单,因此您知道它们不会抛出AttributeError
@property
decorator, which catches AttributeError
and re-throws it as RuntimeError
. 编写自己的@property
装饰器版本,它捕获AttributeError
并将其重新抛出为RuntimeError
。 See also http://blog.devork.be/2011/06/using-getattr-and-property_17.html 另见http://blog.devork.be/2011/06/using-getattr-and-property_17.html
EDIT: In case anyone is considering solution 4 (which I don't recommend), it can be done like this: 编辑:如果有人正在考虑解决方案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)
Then use @property_
instead of @property
in classes that override __getattr__
. 然后使用@property_
代替@property
在覆盖类__getattr__
。
__getattribute__
documentation says: __getattribute__
文档说:
If the class also defines
__getattr__()
, the latter will not be called unless__getattribute__()
either calls it explicitly or raises anAttributeError
. 如果该类还定义了__getattr__()
,则除非__getattribute__()
显式调用它或引发AttributeError
否则不会调用后者。
I read this (by inclusio unius est exclusio alterius ) as saying that attribute access will call __getattr__
if object.__getattribute__
(which is " called unconditionally to implement attribute accesses ") happens to raise AttributeError
- whether directly or inside a descriptor __get__
(eg a property fget); 我读到这个(通过inclusio unius est exclusio alterius )说如果object.__getattribute__
__getattr__
(“ 无条件地实现属性访问 ”)恰好引发AttributeError
- 属性访问将调用__getattr__
- 无论是直接还是在描述符__get__
内部(例如a property fget); note that __get__
should " return the (computed) attribute value or raise an AttributeError
exception ". 请注意__get__
应该“ 返回(计算的)属性值或引发AttributeError
异常 ”。
As an analogy, operator special methods can raise NotImplementedError
whereupon the other operator methods (eg __radd__
for __add__
) will be tried. 作为类比,运算符特殊方法可以引发NotImplementedError
,然后将尝试其他运算符方法(例如__radd__
for __add__
)。
__getattr__
is called when an attribute access fails with an AttributeError. 当属性访问因AttributeError失败时,将调用__getattr__
。 Maybe this is why you think it 'catches' the errors. 也许这就是为什么你认为它“抓住”了错误。 However, it doesn't, it's Python's attribute access functionality that catches them, and then calls __getattr__
. 但是,它没有,它的Python的属性访问功能捕获它们,然后调用__getattr__
。
But __getattr__
itself doesn't catch any errors. 但是__getattr__
本身并没有发现任何错误。 If you raise an AttributeError in __getattr__
you get infinite recursion. 如果在__getattr__
引发AttributeError,则会得到无限递归。
You're doomed anyways when you combine @property
with __getattr__
: 当你将@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))
Gives the following output: 给出以下输出:
Checking for paradise (just for fun)
sorry! life does not exist in Earth
Life in earth: None
When your real problem was with calling Paradise.breasts
. 当你真正的问题是调用Paradise.breasts
。
__getattr__
is always called when an AtributeError
is risen. 当AtributeError
上升时,总是调用__getattr__
。 The content of the exception is ignored. 异常的内容被忽略。
The sad thing is that there's no solution to this problem given hasattr(earth, 'life')
will return True
(just because __getattr__
is defined), but will still be reached by the attribute 'life' as it didn't exist, whereas the real underlying problem is with Paradise.breasts
. 令人遗憾的是,由于hasattr(earth, 'life')
将返回True
(仅因为定义了__getattr__
),因此该问题无法解决,但仍然会通过属性“生命”来达到,因为它不存在,而真正的潜在问题是Paradise.breasts
。
My partial solution involves using a try-except in @property
blocks which are known to hit upon AttributeError
exceptions. 我的部分解决方案涉及在@property
块中使用try-except,这些块已知会遇到AttributeError
异常。
regularly run into this problem because I implement __getattr__
a lot and have lots of @property
methods. 经常遇到这个问题因为我实现了很多__getattr__
并且有很多@property
方法。 Here's a decorator I came up with to get a more useful error message: 这是我想出的装饰器,以获得更有用的错误消息:
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
And use it like this: 并像这样使用它:
class C(object):
def __getattr__(self, name):
...
@property
@replace_attribute_error_with_runtime_error
def complicated_property(self):
...
...
The error message of the underlying exception will include name of the class whose instance raised the underlying AttributeError
. 底层异常的错误消息将包括其实例引发基础AttributeError
的类的名称。 You can also log it if you want to. 如果您愿意,也可以记录它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.