我很难实现属性和__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__
之前尝试的。
我认为当一个属性存在但失败时,仍然会尝试__getattr__
,而不是让属性中的错误通过。如何避免这种情况?
生成的有关未能获取属性'foo'
的初始错误消息已丢失。最终的错误消息'A' object has no attribute 'G'
特别容易引起误解和烦恼。如何实现__getattr__
以查看初始错误?
(编辑)一个相关的问题是同时实现这一点
hasattr(a, 'moo')
会在False
返回hasattr(a, 'G')
时返回True
,或者会引发缺少'moo'
属性的异常。这有意义吗?
答案 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)
然而,它可能导致派生代码中出现的问题比解决的问题多,所以我不知道。