首页 > 解决方案 > 使用 @properties 提高 AttributeError 会产生意想不到的副作用

问题描述

给定一个人为的 Python 类:

class Item(object):
    def __getattr__(self, name):
        print("In __getattr__ looking for '{}'".format(name))
        if name.startswith("exists"):
            return "Here's a dummy string"
        message = "'{}' object has no attribute '{}'".format(type(self).__name__, name)
        raise AttributeError(message)

    def thing_a(self):
        return self.thing_b()

    def thing_b(self):
        return self.nonexistent_thing()

调用Item().thing_a()会产生预期的输出:

In __getattr__ looking for 'nonexistent_thing'
Traceback (most recent call last):
  File "debug.py", line 13, in <module>
    Item().thing_a()
  File "debug.py", line 8, in thing_a
    return self.thing_b()
  File "debug.py", line 11, in thing_b
    return self.nonexistent_thing()
  File "debug.py", line 5, in __getattr__
    raise AttributeError(message)
AttributeError: 'Item' object has no attribute 'nonexistent_thing'

但是,我真的希望thing_a,thing_bnonexistent_thing成为属性。因此,如果我将代码更改为:

class Item(object):
    def __getattr__(self, name):
        print("In __getattr__ looking for '{}'".format(name))
        if name.startswith("exists"):
            return "Here's a dummy string"
        message = "'{}' object has no attribute '{}'".format(type(self).__name__, name)
        raise AttributeError(message)

    @property
    def thing_a(self):
        return self.thing_b

    @property
    def thing_b(self):
        return self.nonexistent_thing

并打电话Item().thing_a,我得到了一个意想不到的结果:

In __getattr__ looking for 'nonexistent_thing'
In __getattr__ looking for 'thing_b'
In __getattr__ looking for 'thing_a'
Traceback (most recent call last):
  File "debug.py", line 15, in <module>
    Item().thing_a
  File "debug.py", line 5, in __getattr__
    raise AttributeError(message)
AttributeError: 'Item' object has no attribute 'thing_a'

呃-哦...print()输出似乎与执行顺序相反,并且消息 forAttributeError是 for thing_a,确实存在。

在我尝试解决这个问题的过程中,我发现更改AttributeError为普通的旧Exception版本可以让一切正常:

In __getattr__ looking for 'nonexistent_thing'
Traceback (most recent call last):
  File "debug.py", line 15, in <module>
    Item().thing_a
  File "debug.py", line 9, in thing_a
    return self.thing_b
  File "debug.py", line 13, in thing_b
    return self.nonexistent_thing
  File "debug.py", line 5, in __getattr__
    raise Exception(message)
Exception: 'Item' object has no attribute 'nonexistent_thing'

所以,它AttributeError本身似乎不能很好地与 一起使用@property,可能是因为它在某个地方被property对象抓住了。但AttributeError似乎是最合适的异常情况__getattr__,并且在没有子类化的情况下提高情况并不理想Exception

有没有人能够对这个问题有所了解,并且知道一种@property和谐合作的__getattr__方法?AttributeError

我已经在 Python 2.7 和 3.6 中尝试过。

编辑:添加(人为)逻辑__getattr__以响应 ifname开头"exists"。我确实希望它失败nonexistent_thing,但我希望它AttributeError在正确的时间以正确的方式加注name

标签: python

解决方案


谜题中缺少的部分@property描述符(实现__get__//参与属性查找的对象)。__set____del__

描述符的实现是通过__getattribute__魔术方法完成的。

文档中实现的重要一点(强调我的):

无条件调用以实现类实例的属性访问。如果该类还定义了__getattr__(),则不会调用后者,除非__getattribute__()显式调用它或引发 AttributeError

为了进一步说明您的示例:

class Item(object):
    def __getattribute__(self, name):
        print(f'__getattribute__ {name}')
        try:
            return super().__getattribute__(name)
        except Exception as e:
            print(f'{e} during __getattribute__ {name}')
            raise

    def __getattr__(self, name):
        print("In __getattr__ looking for '{}'".format(name))
        # useful logic here which might return a value...
        message = "'{}' object has no attribute '{}'".format(type(self).__name__, name)
        raise AttributeError(message)

    @property
    def thing_a(self):
        return self.thing_b

    @property
    def thing_b(self):
        return self.nonexistent_thing


Item().thing_a

调用顺序如下:

- __getattribute__(thing_a)
  - property.__get__(...)
    - thing_a
      - __getattriute__(thing_b)
        - property.__get__(...)
          - thing_B
            - __getattribute__(nonexistent_thing)
            - (raised AE) -> __getattr__(nonexistent_thing)
      - (raised AE) -> __getattr_(thing_b)
- (raised AE) -> getattr(thing_a)

在描述符解析期间对 s的调用__getattr__是对 s 的响应。AttributeError__getattribute__

以下是上述 python 片段的输出:

__getattribute__ thing_a
__getattribute__ thing_b
__getattribute__ nonexistent_thing
'Item' object has no attribute 'nonexistent_thing' during __getattribute__ nonexistent_thing
In __getattr__ looking for 'nonexistent_thing'
'Item' object has no attribute 'nonexistent_thing' during __getattribute__ thing_b
In __getattr__ looking for 'thing_b'
'Item' object has no attribute 'thing_b' during __getattribute__ thing_a
In __getattr__ looking for 'thing_a'
Traceback (most recent call last):
  File "t.py", line 25, in <module>
    Item().thing_a
  File "t.py", line 14, in __getattr__
    raise AttributeError(message)
AttributeError: 'Item' object has no attribute 'thing_a'

推荐阅读