AttributeErrors:@属性 和 __getattr__ 之间的意外交互
AttributeErrors: undesired interaction between @property and __getattr__
我对 @property
中提出的 AttributeErrors
与 python 中的 __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 的回答中的想法,我目前最好的解决方案如下。非常感谢有关如何摆脱 AttributeError
的 __main__.
前缀和回溯中对 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'
如果您愿意专门使用新样式 类,您可以重载 __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'
您可以创建一个看起来像 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
.
省略了这一行
我相当确定您问题的最佳解决方案包含 and you can see the 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
这样,当出现您意想不到的错误时,您会立即知道!
我对 @property
中提出的 AttributeErrors
与 python 中的 __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 的回答中的想法,我目前最好的解决方案如下。非常感谢有关如何摆脱 AttributeError
的 __main__.
前缀和回溯中对 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'
如果您愿意专门使用新样式 类,您可以重载 __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'
您可以创建一个看起来像 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
.
我相当确定您问题的最佳解决方案包含 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
这样,当出现您意想不到的错误时,您会立即知道!