5

我是 MongoDB 的一个简单数据库层(受minimongo启发)的作者——而且目前几乎绝对是唯一的用户,如果在多个项目中,称为kale. 我目前__getattr__在模型基类中的使用导致了一些难以跟踪的错误。

我遇到的问题是去年 6 月 David Halter 在这个网站上简明扼要地阐述的。讨论很有趣,但没有提供任何解决方案。

简而言之:

>>> 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
>>>

请注意,这种矛盾的行为并不是我阅读官方 python 文档所期望的:

object.__getattr__(self, name)

当属性查找在通常的位置没有找到该属性时调用(即它不是实例属性,也不是在 self 的类树中找到)。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是为构建模型提供一个基类。因此,基本模型代码对最终程序员来说是隐藏的,并且像这样掩盖错误的原因并不理想。

最终程序员(咳咳)可能会选择@property在他们的模型上使用描述符,并且他们的代码应该以他们期望的方式工作和失败。

问题

如何允许AttributeErrors 通过已定义的基类传播__getattr__

4

3 回答 3

5

基本上,你不能——或者至少,不能以简单和万无一失的方式。正如您所指出的,AttributeErrorPython 用来确定属性是否“在通常的地方找到”的机制。尽管__getattr__文档没有提到这一点,但在文档中更清楚地说明了这一点__getattribute__,如您已链接到的问题的答案中所述。

您可以覆盖__getattribute__并捕获AttributeError那里,但是如果您捕获了一个,您将没有明显的方法来判断它是“真正的”错误(意味着确实没有找到该属性),还是用户代码在执行期间引发的错误查找过程。从理论上讲,您可以检查回溯以查找特定事物,或进行各种其他黑客攻击以尝试验证属性的存在,但这些方法将比现有行为更加脆弱和危险。

另一种可能性是编写您自己的基于 的描述符property,但会捕获 AttributeErrors 并将它们重新提升为其他内容。但是,这将要求您使用此属性替换而不是 builtin property。此外,这意味着从内部描述符方法引发的 AttributeErrors 不会作为 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 被 ValueErrors 替换。如果您只想确保异常“突破”属性访问机制并可以传播到下一个级别,这可能会很好。但是,如果您有复杂的异常捕获代码期望看到一个 AttributeError,它会错过这个错误,因为异常类型已经改变。

(另外,这个例子显然只处理属性 getter,而不是 setter,但应该清楚如何扩展这个想法。)

我想作为此解决方案的扩展,您可以将此MyProp想法与自定义__getattribute__. 基本上,您可以定义一个自定义异常类,例如PropertyAttributeError,并让属性替换将 AttributeError 重新提升为 PropertyAttributeError。然后,在您的自定义__getattribute__中,您可以捕获 PropertyAttributeError 并将其重新引发为 AttributeError。基本上MyProp__getattribute__可以充当“分流器”,通过将错误从 AttributeError 转换为其他内容,然后在“安全”时再次将其转换回 AttributeError 来绕过 Python 的正常处理。但是,我的感觉是这样做不值得,因为__getattribute__可能会对性能产生重大影响。

一个小附录:在 Python 错误跟踪器上已经提出了一个错误,并且最近有关于可能解决方案的活动,因此它可能会在未来的版本中得到修复。

于 2013-03-14T04:53:55.170 回答
2

您的代码中会发生什么:

第一个案例A

>>>print(A().a)

  1. 创建一个实例A
  2. 'A'访问实例上调用的属性

现在python,按照它的数据模型,试图找到A.ausing object.__getattribute__(因为你还没有提供你的 custom __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_threefailed(no threein things) 带有 AttributeError,现在您__getattr__尝试查找get_thing_threein ,这也失败了 -因为它具有更高的优先级,所以things您会得到异常。get_thing_three

你可以做什么 :

你必须写自定义__getattribute____getattr__两者。但是,在大多数情况下,它不会让您走得太远,并且使用您的代码的其他人不会期望某些自定义数据协议。

好吧,我有一个建议给你(我写了一个我在内部使用的粗略的 mongodb orm):不要依赖你__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
于 2013-03-14T04:54:08.377 回答
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.
>>> 

至少有了这个,我可以提供一条错误消息,提示如何追踪问题,而不是看起来像问题......

不过我还不满意。我很乐意接受可以做得更好的答案!

于 2013-03-14T06:52:49.237 回答