使用property时,在__getattr__中正确处理AttributeError

Ale*_*sov 7 python

我在实现属性__getattr__时遇到了困难,因此当发生错误时,会正确报告.这是我的MWE(python 3.6):

class A:

    @property
    def F(self):
        return self.moo # here should be an error

    @property
    def G(self):
        return self.F

    def __getattr__(self, name):
        print('call of __getattr__ with name =', name)
        if name == 'foo':
            return 0
        raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))

a = A()
print(a.G)
Run Code Online (Sandbox Code Playgroud)

输出如下:

call of __getattr__ with name = moo
call of __getattr__ with name = F
call of __getattr__ with name = G
Traceback (most recent call last):
  line 18 in <module>
    print(a.G)
  line 15, in __getattr__
    raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))
AttributeError: 'A' object has no attribute 'G'
Run Code Online (Sandbox Code Playgroud)

但应该提出的错误是:

AttributeError: 'A' object has no attribute 'moo'
Run Code Online (Sandbox Code Playgroud)

我知道在无错误的场景中调用__dict__之前尝试过的属性和属性__getattr__.

  1. 我觉得当一个属性存在但失败时,__getattr__仍然会尝试而不是让属性中的错误通过.如何避免这种情况?

  2. 生成的有关未能获取属性的初始错误消息'foo'已丢失.最后的错误消息'A' object has no attribute 'G'特别容易引起误解和烦扰.如何实现__getattr__才能看到初始错误?

  3. (编辑)一个相关的问题是同时实现 hasattr(a, 'moo')返回,False同时hasattr(a, 'G')返回True或引发缺失'moo'属性的异常.那有意义吗?

Oli*_*çon 8

怎么了?

首先,有点了解为什么会发生这种情况.来自以下文件__getattr__:

当默认属性访问因AttributeError [...]或__get__()name属性失败时调用,引发AttributeError.

在这种情况下,由于您正在使用@property,我们正在尝试恢复时AttributeError__get__属性方法中提取.这就是你的调用栈在那一刻的样子.Fself.moo

__main__
a.G.__get__
a.F.__get__
a.__getattr__ # called with 'moo' <-- this is where the error is raised
Run Code Online (Sandbox Code Playgroud)

属性getter协议看到从内部引发的错误a.F.__get__,因此它在调用时回退a.__getattr__('F'),尽管由于错误而引发了错误'moo'.然后发生同样的事情a.G.__get__

这种行为在Python中认为是正常的,因为无法返回值的最顶层属性确实存在a.G.

现在你想要的是AttributeError通过一种__get__方法来提升而不是被抓住.要做到这一点,你需要不要有一个__getattr__方法.

因此,在这种特殊情况下,您想要使用的是__getattribute__.

当然,使用此解决方案,您必须确保自己不要覆盖现有属性.

class A:

    @property
    def F(self):
        return self.moo # here should be an error

    @property
    def G(self):
        return self.F

    def __getattribute__(self, name):
        print('call of __getattribute__ with name =', name)
        if name == 'foo':
            return 0
        else:
            return super().__getattribute__(name)
Run Code Online (Sandbox Code Playgroud)

A().G
Run Code Online (Sandbox Code Playgroud)

产量

call of __getattribute__ with name = G
call of __getattribute__ with name = F
call of __getattribute__ with name = moo

Traceback (most recent call last):
...
AttributeError: 'A' object has no attribute 'moo'
Run Code Online (Sandbox Code Playgroud)