使用属性

时间:2018-05-26 11:16:25

标签: 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)

输出如下:

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'

但应该提出的错误是:

AttributeError: 'A' object has no attribute 'moo'

我知道__dict__中的属性和属性是在无错误的情况下调用__getattr__之前尝试的。

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

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

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

3 个答案:

答案 0 :(得分:7)

发生了什么事?

首先,要了解为何会发生这种情况。来自__getattr__上的文档:

  

当默认属性访问失败并且名称属性的AttributeError [...]或__get__()引发AttributeError时调用。

在这种情况下,由于您使用的是@property,因此我们会在尝试恢复{AttributeError时查看从__get__方法获得的F self.moo 1}}。这就是你的调用栈在那一刻的样子。

__main__
a.G.__get__
a.F.__get__
a.__getattr__ # called with 'moo' <-- this is where the error is raised

属性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)

实施例

A().G

输出

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'

答案 1 :(得分:1)

这是一个hacky解决方案,用另一种异常类型替换AttributeError

from functools import wraps

def no_AttributeError(f):
    @wraps(f)
    def wrapped(self):
        try:
            return f(self)
        except AttributeError as e:
            raise Exception('AttributeError inside a property getter') from e
    return wrapped

class A:

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

    @property
    @no_AttributeError
    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)

这导致以下输出:

call of __getattr__ with name = moo
Traceback (most recent call last):
  File ".\test_getattr_redir.py", line 7, in wrapped
    return f(self)
  File ".\test_getattr_redir.py", line 17, in F
    return self.moo # here should be an error
  File ".\test_getattr_redir.py", line 28, in __getattr__
    raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))
AttributeError: 'A' object has no attribute 'moo'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File ".\test_getattr_redir.py", line 31, in <module>
    print(a.G)
  File ".\test_getattr_redir.py", line 7, in wrapped
    return f(self)
  File ".\test_getattr_redir.py", line 22, in G
    return self.F
  File ".\test_getattr_redir.py", line 9, in wrapped
    raise Exception('AttributeError inside a property getter') from e
Exception: AttributeError inside a property getter

作为附录,为了明确Python为什么会这样做,这里摘录自the documentation

  当默认属性访问失败并显示__getattr__时,

调用[AttributeError] __getattribute__()引发AttributeError,因为name不是实例属性或self的类树中的属性; __get__()属性的name会引发AttributeError )。此方法应返回(计算的)属性值或引发AttributeError异常。

(看起来你知道这一点,但我觉得把它写给遇到同样问题的其他人很好。)

这意味着当self.moo提出AttributeError时,会导致A.__getattr__(a, 'F')被调用,从而产生另一个AttributeError

答案 2 :(得分:0)

鉴于上面的答案,我已经针对我们无法更改的基类((Lifecycle) sendToDb).stop(); 已经定义__getattr__的情况尝试了以下解决方案。

P

它复制了python在查找属性时所执行的操作:保持调用和语义的顺序。它只将最终错误消息更改为

class P: def __getattr__(self, name): print('call of __getattr__ with name =', name) if name == 'foo': return 0 raise AttributeError("Cannot recover attribute '{}'".format(name)) class A(P): e = None @property def F(self): return self.moo @property def G(self): return self.F def __getattr__(self, name): raise A.e def __getattribute__(self, name): try: return object.__getattribute__(self, name) except AttributeError as e1: try: return P.__getattr__(self, name) except AttributeError as e2: A.e = AttributeError(str(e1) + ' -> ' + str(e2)) raise AttributeError a = A() print(a.G)

然而,它可能导致派生代码中出现的问题比解决的问题多,所以我不知道。