【问题标题】:AttributeErrors: undesired interaction between @property and __getattr__AttributeErrors:@property 和 __getattr__ 之间的不良交互
【发布时间】:2016-08-03 04:07:35
【问题描述】:

我对@9​​87654322@ 中的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'

【问题讨论】:

  • 您必须在类定义中执行 __qualname__ = "AttributeError" 才能删除 __main__ 部分,但请相信我,您确实不希望错误只是说 AttributeError 因为那时您可能会感到困惑,为什么 except AttributeError 没有捕获 AttributeError。

标签: python properties attributeerror getattr


【解决方案1】:

如果你愿意专门使用新式类,你可以重载__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'

【讨论】:

  • 谢谢,我对这个解决方案的问题是对其他属性的性能影响。 (其中一些可以循环访问。)
  • @ARF,如果性能是一个问题,那么获取对经常访问的属性的本地引用。我建议这样做,即使没有通过重载__getattribute__ 来稍微放慢速度。
  • 您的意思是a = test.a,然后使用a 而不是test.a?
  • @ARF 是的,我实际上在我的广告中宣传了凯文的答案,因为它远远优于我的。
【解决方案2】:

您可以创建一个看似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

这样,当出现意想不到的问题时,您会立即知道!

【讨论】:

  • 这并没有真正让事情变得更好:堆栈跟踪仍然很遥远!请注意,错误出现在函数 deeply_nested_factory_fn 中,而不是函数 inner 中,正如您的解决方案生成的堆栈跟踪所建议的那样。 - 也就是说,如果堆栈跟踪问题可以解决,我真的很喜欢你的想法,它可以避免 sys.exit(1)...。
  • 经过一番摸索之后,我想出了一个我不太满意的解决方案。如果您仍然感兴趣,请查看修改后的问题。
  • 这将阻止人们使用except AttributeError 来捕获此异常。
  • @user2357112 这是一个很好的观点。我将不得不考虑更多。
  • @TadhgMcDonald-Jensen 实际上,对于我的应用程序,您说明的 hasattr 行为是可取的。我遇到了hasattr 在if hasattr(...): .... else: .... 块中“静默”失败的问题——不是因为属性未定义,而是因为实现存在错误。当然,其他代码应该完全避免hasattr,而只需使用try: some_obj.attr except AttributeError: .... 块...
猜你喜欢
  • 2012-06-22
  • 1970-01-01
  • 1970-01-01
  • 2012-09-06
  • 2016-11-11
  • 2016-05-15
  • 2015-03-31
  • 2014-06-28
  • 2015-07-17
相关资源
最近更新 更多