14

I'm struggling with __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() could just use A.__dict__. Any thoughts?

4

6 回答 6

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__()因此被称为“最后的手段”。

它没有明确记录,但可以计入“当属性查找未在通常位置找到属性时调用”。

于 2012-06-20T10:08:21.960 回答
9

在同一个类中使用__getattr__和属性是危险的,因为它可能导致非常难以调试的错误。

如果属性的 getter 抛出AttributeError,则 会AttributeError被静默捕获并被__getattr__调用。通常,这会导致__getattr__失败并出现异常,但如果您非常不走运,则不会,您甚至无法轻松地将问题追溯到__getattr__.

除非你的属性 getter 是微不足道的,否则你永远不能 100% 确定它不会抛出AttributeError. 异常可能会被抛出几个级别。

这是您可以执行的操作:

  1. __getattr__避免在同一个类中使用属​​性。
  2. try ... except为所有不平凡的属性获取器添加一个块
  3. 保持属性 getter 简单,这样你就知道他们不会抛出AttributeError
  4. 编写您自己的@property装饰器版本,将其捕获AttributeError并重新抛出为RuntimeError.

另见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__

于 2014-06-17T10:03:51.050 回答
6

__getattribute__文档说:

如果该类还定义了__getattr__(),则不会调用后者,除非__getattribute__()显式调用它或引发AttributeError.

我读到这个(通过inclusio unius est exclusio alterius)说属性访问调用__getattr__if object.__getattribute__(这是“无条件地调用以实现属性访问”)碰巧引发AttributeError- 无论是直接还是在描述符内部__get__(例如属性 fget);请注意,__get__应该“返回(计算的)属性值或引发AttributeError异常”。

作为一个类比,运算符特殊方法可以引发NotImplementedError其他运算符方法(例如__radd__for __add__)将被尝试。

于 2012-06-20T10:46:11.267 回答
4

__getattr__当属性访问失败并出现 AttributeError 时调用。也许这就是您认为它“捕获”错误的原因。但是,它没有,它是 Python 的属性访问功能捕获它们,然后调用__getattr__.

__getattr__它本身并没有捕捉到任何错误。如果您提出 AttributeError ,__getattr__您将获得无限递归。

于 2012-06-20T10:11:49.543 回答
1

经常遇到这个问题,因为我实现__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。如果你愿意,你也可以记录它。

于 2017-06-13T11:20:11.210 回答
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__总是在 anAtributeError上升时被调用。异常的内容被忽略。

可悲的是,给定的这个问题没有解决方案hasattr(earth, 'life')将返回True(只是因为__getattr__已定义),但仍然会通过属性 'life' 到达,因为它不存在,而真正的潜在问题是 with Paradise.breasts

@property我的部分解决方案涉及在已知会遇到AttributeError异常的块中使用 try-except 。

于 2016-10-07T15:36:00.503 回答