我在AttributeErrors
中与@property
结合使用__getattr__()
时遇到问题:
示例代码:
>>> def deeply_nested_factory_fn():
... a = 2
... return a.invalid_attr
...
>>> class Test(object):
... def __getattr__(self, name):
... if name == 'abc':
... return 'abc'
... raise AttributeError("'Test' object has no attribute '%s'" % name)
... @property
... def my_prop(self):
... return deeply_nested_factory_fn()
...
>>> test = Test()
>>> test.my_prop
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __getattr__
AttributeError: 'Test' object has no attribute 'my_prop'
就我而言,这是一个极具误导性的错误消息,因为它隐藏了deeply_nested_factory_fn()
出错的事实。
根据Tadhg McDonald-Jensen的回答,我目前最好的解决方案如下。关于如何摆脱__main__.
AttributeError
前缀以及追溯中attributeErrorCatcher
的引用的任何提示都将非常感激。
>>> def catchAttributeErrors(func):
... AttributeError_org = AttributeError
... def attributeErrorCatcher(*args, **kwargs):
... try:
... return func(*args, **kwargs)
... except AttributeError_org as e:
... import sys
... class AttributeError(Exception):
... pass
... etype, value, tb = sys.exc_info()
... raise AttributeError(e).with_traceback(tb.tb_next) from None
... return attributeErrorCatcher
...
>>> def deeply_nested_factory_fn():
... a = 2
... return a.invalid_attr
...
>>> class Test(object):
... def __getattr__(self, name):
... if name == 'abc':
... # computing come other attributes
... return 'abc'
... raise AttributeError("'Test' object has no attribute '%s'" % name)
... @property
... @catchAttributeErrors
... def my_prop(self):
... return deeply_nested_factory_fn()
...
>>> class Test1(object):
... def __init__(self):
... test = Test()
... test.my_prop
...
>>> test1 = Test1()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __init__
File "<stdin>", line 11, in attributeErrorCatcher
File "<stdin>", line 10, in my_prop
File "<stdin>", line 3, in deeply_nested_factory_fn
__main__.AttributeError: 'int' object has no attribute 'invalid_attr'
答案 0 :(得分:2)
如果您愿意专门使用新式课程,可以重载__getattribute__
而不是__getattr__
:
class Test(object):
def __getattribute__(self, name):
if name == 'abc':
return 'abc'
else:
return object.__getattribute__(self, name)
@property
def my_prop(self):
return deeply_nested_factory_fn()
现在您的堆栈跟踪将正确提及deeply_nested_factory_fn
。
Traceback (most recent call last):
File "C:\python\myprogram.py", line 16, in <module>
test.my_prop
File "C:\python\myprogram.py", line 10, in __getattribute__
return object.__getattribute__(self, name)
File "C:\python\myprogram.py", line 13, in my_prop
return deeply_nested_factory_fn()
File "C:\python\myprogram.py", line 3, in deeply_nested_factory_fn
return a.invalid_attr
AttributeError: 'int' object has no attribute 'invalid_attr'
答案 1 :(得分:1)
您可以创建一个看似AttributeError
的自定义异常,但不会触发__getattr__
,因为它实际上不是AttributeError
。
更新:通过在重新引发错误之前重新分配.__traceback__
属性,大大改善了回溯消息:
class AttributeError_alt(Exception):
@classmethod
def wrapper(err_type, f):
"""wraps a function to reraise an AttributeError as the alternate type"""
@functools.wraps(f)
def alt_AttrError_wrapper(*args,**kw):
try:
return f(*args,**kw)
except AttributeError as e:
new_err = err_type(e)
new_err.__traceback__ = e.__traceback__.tb_next
raise new_err from None
return alt_AttrError_wrapper
然后将属性定义为:
@property
@AttributeError_alt.wrapper
def my_prop(self):
return deeply_nested_factory_fn()
,您将收到的错误消息如下所示:
Traceback (most recent call last):
File ".../test.py", line 34, in <module>
test.my_prop
File ".../test.py", line 14, in alt_AttrError_wrapper
raise new_err from None
File ".../test.py", line 30, in my_prop
return deeply_nested_factory_fn()
File ".../test.py", line 20, in deeply_nested_factory_fn
return a.invalid_attr
AttributeError_alt: 'int' object has no attribute 'invalid_attr'
注意 是raise new_err from None
的一行,但它位于属性调用中的行之上。 return f(*args,**kw)
也会有一行,但.tb_next
会省略。
我相当确定问题的最佳解决方案有already been suggested,您可以看到我的答案的previous revision,为什么我认为这是最佳选择。虽然老实说如果有一个错误被错误地压制,那么就将一个血腥的RuntimeError
链接到一个隐藏的那个:
def assert_no_AttributeError(f):
@functools.wraps(f)
def assert_no_AttrError_wrapper(*args,**kw):
try:
return f(*args,**kw)
except AttributeError as e:
e.__traceback__ = e.__traceback__.tb_next
raise RuntimeError("AttributeError was incorrectly raised") from e
return assert_no_AttrError_wrapper
然后如果你用这个装饰你的财产,你会得到这样的错误:
Traceback (most recent call last):
File ".../test.py", line 27, in my_prop
return deeply_nested_factory_fn()
File ".../test.py", line 17, in deeply_nested_factory_fn
return a.invalid_attr
AttributeError: 'int' object has no attribute 'invalid_attr'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File ".../test.py", line 32, in <module>
x.my_prop
File ".../test.py", line 11, in assert_no_AttrError_wrapper
raise RuntimeError("AttributeError was incorrectly raised") from e
RuntimeError: AttributeError was incorrectly raised
虽然如果你期望引发一个AttributeError只有一件事,那么你可能只想重载__getattribute__
以检查所有查找的任何特殊错误:
def __getattribute__(self,attr):
try:
return object.__getattribute__(self,attr)
except AttributeError as e:
if str(e) == "{0.__class__.__name__!r} object has no attribute {1!r}".format(self,attr):
raise #normal case of "attribute not found"
else: #if the error message was anything else then it *causes* a RuntimeError
raise RuntimeError("Unexpected AttributeError") from e
这种方式出现问题时,你没想到你会立刻知道它!