Python:为什么__getattr__捕获AttributeErrors?

时间:2012-06-20 09:48:45

标签: python exception exception-handling python-3.x python-2.7

我在与__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__。有什么想法吗?

6 个答案:

答案 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。异常可能会被抛出几个层次。

以下是您可以做的事情:

  1. 避免在同一个班级中使用属性和__getattr__
  2. try ... except块添加到所有非常重要的属性getter
  3. 保持属性getter简单,因此您知道他们不会抛出AttributeError
  4. 编写您自己的@property装饰器版本,该装置会捕获AttributeError并将其重新抛出为RuntimeError
  5. 另见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)

当属性访问因AttributeError失败时,将调用

__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的类的名称。 如果您愿意,也可以记录它。