我是作者 - 目前几乎绝对是唯一的用户,如果在多个项目中,a simple database layer(受minimongo启发)的MongoDB称为kale
。我目前在模型基类中使用__getattr__
导致了一些难以跟踪的错误。
我在去年六月由David Halter在本网站上简明扼要地遇到了问题was articulated。讨论很有意思,但没有提供解决方案。
简而言之:
>>> class A(object):
... @property
... def a(self):
... print "We're here -> attribute lookup found 'a' in one of the usual places!"
... raise AttributeError
... return "a"
...
... def __getattr__(self, name):
... print "We're here -> attribute lookup has not found the attribute in the usual places!"
... print('attr: ', name)
... return "not a"
...
>>> print(A().a)
We're here -> attribute lookup found 'a' in one of the usual places!
We're here -> attribute lookup has not found the attribute in the usual places!
('attr: ', 'a')
not a
>>>
请注意,这种矛盾的行为并不是我对阅读the official python documentation所期望的:
object.__getattr__(self, name)
当属性查找未找到属性时调用 通常的地方(即它不是一个实例属性,也没有找到 自我的类树。 name是属性名称。
(如果他们提到AttributeError
是“属性查找”知道在“通常的地方”中是否找到某个属性的方法,那将是很好的。澄清括号在我看来最好不完整的。)
实际上,这导致了在@property
描述符事件中引发AttributeError的情况下跟踪编程错误导致的错误的问题。
>>> class MessedAttrMesser(object):
... things = {
... 'one': 0,
... 'two': 1,
... }
...
... def __getattr__(self, attr):
... try:
... return self.things[attr]
... except KeyError as e:
... raise AttributeError(e)
...
... @property
... def get_thing_three(self):
... return self.three
...
>>>
>>> blah = MessedAttrMesser()
>>> print(blah.one)
0
>>> print(blah.two)
1
>>> print(blah.get_thing_three)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in __getattr__
AttributeError: 'get_thing_three'
>>>
在这种情况下,通过检查整个班级来发现是非常明显的。但是,如果您依赖堆栈跟踪AttributeError: 'get_thing_three'
中的消息,则没有任何意义,因为很明显,get_thing_three
看起来像是一个有效的属性。
kale
的目的是为构建模型提供基类。因此,基本模型代码远离最终程序员隐藏,并且掩盖这样的错误原因并不理想。
最终程序员( cough me)可能会选择在他们的模型上使用@property
描述符,并且他们的代码应该以他们的方式运行和失败期望的。
问题
如何允许AttributeError
通过已定义__getattr__
的基类传播?
答案 0 :(得分:5)
基本上,你不能 - 或者至少,不能以简单和万无一失的方式。如您所述,AttributeError
是Python用于确定属性是否“在通常位置找到”的机制。虽然__getattr__
文档没有提到这一点,但在__getattribute__
的文档中更清楚一些,如this answer中对已经链接到的问题所述。
你可以覆盖__getattribute__
并在那里捕捉AttributeError
,但是如果你抓到了property
,你就没有明显的方法来判断它是否是一个“真正的”错误(意味着属性真的不是'发现),或在查找过程中由用户代码引发的错误。从理论上讲,你可以检查寻找特定事物的追溯,或者做各种其他黑客来试图验证属性的存在,但这些方法将比现有的行为更加脆弱和危险。
另一种可能性是编写自己的基于property
的描述符,但捕获AttributeErrors并将其重新引发为其他内容。但是,这将要求您使用此属性替换而不是内置class MyProp(property):
def __get__(self, obj, cls):
try:
return super(MyProp, self).__get__(obj, cls)
except AttributeError:
raise ValueError, "Property raised AttributeError"
class A(object):
@MyProp
def a(self):
print "We're here -> attribute lookup found 'a' in one of the usual places!"
raise AttributeError
return "a"
def __getattr__(self, name):
print "We're here -> attribute lookup has not found the attribute in the usual places!"
print('attr: ', name)
return "not a"
>>> A().a
We're here -> attribute lookup found 'a' in one of the usual places!
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
A().a
File "<pyshell#6>", line 6, in __get__
raise ValueError, "Property raised AttributeError"
ValueError: Property raised AttributeError
。此外,这意味着从描述符内部方法引发的AttributeErrors不会作为AttributeErrors传播,而是作为其他东西传播(无论你用它们替换它们)。这是一个例子:
MyProp
此处AttributeErrors将替换为ValueErrors。如果你想要的只是确保异常“突破”属性访问机制并且可以传播到下一级别,这可能没问题。但是,如果您有复杂的异常捕获代码,期望看到一个AttributeError,它将错过此错误,因为异常类型已更改。
(另外,这个例子显然只涉及属性获取者,而不是setter,但应该清楚如何扩展这个想法。)
我想作为此解决方案的扩展,您可以将此__getattribute__
提示与自定义PropertyAttributeError
结合使用。基本上,您可以定义一个自定义异常类,比如__getattribute__
,并将属性替换重新引用AttributeError作为PropertyAttributeError。然后,在您的自定义MyProp
中,您可以捕获PropertyAttributeError并将其重新引发为AttributeError。基本上__getattribute__
和__getattribute__
可以充当“分流器”,通过将错误从AttributeError转换为其他东西来绕过Python的正常处理,然后一旦“安全”到它就再次将其转换回AttributeError。这样做。但是,我觉得这样做是不值得的,因为{{1}}会对性能产生重大影响。
一个小小的附录:a bug已经在Python错误跟踪器上提出了这个问题,并且最近有关于可能解决方案的活动,因此可能会在将来的版本中修复它。
答案 1 :(得分:2)
您的代码中会发生什么:
第A
课的第一个案例:
>>>print(A().a)
A
'A'
的属性
醇>
现在python,按照其数据模型,尝试使用A.a
找到object.__getattribute__
(因为您还没有提供自定义__getattribute__
)
但是:
@property
def a(self):
print "We're here -> attribute lookup found 'a' in one of the usual places!"
raise AttributeError # <= an AttributeError is raised - now python resorts to '__getattr__'
return "a" # <= this code is unreachable
所以,由于__getattribute__
查询以AttributeError
结尾,因此会调用__getattr__
:
def __getattr__(self, name):
... print "We're here -> attribute lookup has not found the attribute in the usual places!"
... print('attr: ', name)
... return "not a" #it returns 'not a'
第二段代码中会发生什么:
您可以blah.get_thing_three
访问__getattribute__
。由于get_thing_three
因属性错误而失败(three
中没有things
),现在您的__getattr__
尝试在get_thing_three
中查找things
,这也会失败 - get_thing_three
会获得例外,因为它具有更高的优先级。
你能做什么:
您必须同时编写自定义__getattribute__
和__getattr__
。但是,在大多数情况下,它不会让你走得太远,其他人使用你的代码不会期望一些自定义数据协议。
__getattr__
。使用直接访问你班级内的文件(我认为它不会伤害任何封装)。这是我的榜样:
class Model(object):
_document = { 'a' : 1, 'b' : 2 }
def __getattr__(self, name):
r"""syntactic sugar for those who are using this class externally.
>>>foo = Model()
>>>foo.a
1"""
@property
def ab_sum(self):
try :
return self._document[a] + self._document[b]
except KeyError:
raise #something that isn't AttributeError
答案 2 :(得分:0)
我希望我还能在这里得到更多的想法。但是,没有什么能满足我的要求!这可能是不可能的,但至少我有点接近:
>>> class GetChecker(dict):
... def __getattr__(self, attr):
... try:
... return self[attr]
... except KeyError as e:
... if hasattr(getattr(type(self), attr), '__get__'):
... raise AttributeError('ooh, this is an tricky error.')
... else:
... raise AttributeError(e)
...
... @property
... def get_thing_three(self):
... return self.three
...
>>>
>>> blah = GetChecker({'one': 0})
>>> print(blah.one)
0
>>> print(blah.lalala)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __getattr__
AttributeError: type object 'GetChecker' has no attribute 'lalala'
>>> print(blah.get_thing_three)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __getattr__
AttributeError: ooh, this is an tricky error.
>>>
至少我可以提供一条错误消息,暗示如何追查问题,而不是看起来像问题...
但是,我不满意。我很乐意接受一个可以做得更好的答案!