我在与__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__
documentation)。 getattr()
只能使用A.__dict__
。有什么想法吗?
答案 0 :(得分:9)
我刚刚将代码更改为
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__()
被称为“最后的手段”。
没有明确记录,但可以计入“在通常位置找不到属性的属性查询时调用”。
答案 1 :(得分:8)
在同一个类中使用__getattr__
和属性是危险的,因为它可能导致很难调试的错误。
如果属性的getter抛出AttributeError
,则AttributeError
会被静默捕获,并调用__getattr__
。通常,这会导致__getattr__
失败并出现异常,但如果您非常不走运,则不会,并且您甚至无法轻松将问题追溯回__getattr__
。
除非你的财产获取者是微不足道的,否则你永远不能100%确定它不会抛出AttributeError
。异常可能会被抛出几个层次。
以下是您可以做的事情:
__getattr__
。try ... except
块添加到所有非常重要的属性getter 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__
。
答案 2 :(得分:6)
__getattribute__
documentation说:
如果该课程还定义了
__getattr__()
,则除非__getattribute__()
明确地调用它或引发AttributeError
,否则不会调用后者。
我读了这篇文章(通过 inclusio unius est exclusio alterius ),如果__getattr__
(即“{1}}属性访问将调用object.__getattribute__
无条件地调用实现属性访问“)恰好引发AttributeError
- 无论是直接还是在描述符__get__
内部(例如属性fget);请注意__get__
应该“返回(计算的)属性值或引发AttributeError
例外”。
作为类比,运算符特殊方法可以引发NotImplementedError
,然后将尝试其他运算符方法(例如__radd__
的{{1}})。
答案 3 :(得分:4)
__getattr__
。也许这就是为什么你认为它“抓住”了错误。然而,它没有,它的Python的属性访问功能捕获它们,然后调用__getattr__
。
但是__getattr__
本身并没有发现任何错误。如果在__getattr__
中引发AttributeError,则会得到无限递归。
答案 4 :(得分:0)
将@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__
上升时,始终会调用{p> AtributeError
。异常的内容将被忽略。
令人遗憾的是,由于hasattr(earth, 'life')
将返回True
(仅因为__getattr__
已定义),因此仍无法解决此问题,但仍会通过“生命”属性达到此问题因为它不存在,而真正的潜在问题是Paradise.breasts
。
我的部分解决方案涉及在@property
块中使用try-except,这些块已知会遇到AttributeError
个例外。
答案 5 :(得分:0)
经常遇到这个问题,因为我经常实施__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
的类的名称。
如果您愿意,也可以记录它。