简体   繁体   English

Python:为什么__getattr__捕获AttributeErrors?

[英]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: 这是你可以做的:

  1. Avoid using properties and __getattr__ in the same class. 避免在同一个类中使用属​​性和__getattr__
  2. Add a try ... except block to all property getters that are not trivial try ... except块添加到所有非平凡的属性getter中
  3. Keep property getters simple, so you know they won't throw AttributeError 保持属性getter简单,因此您知道它们不会抛出AttributeError
  4. Write your own version of the @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 an AttributeError . 如果该类还定义了__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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM