English 中文(简体)
如何实现__getattribute__而不出现无限递归错误?
原标题:
  • 时间:2008-12-16 16:04:38
  •  标签:

我想在一个类中覆盖对一个变量的访问权限,但返回所有其他变量。我该如何使用 __getattribute__ 实现这一点?

我尝试了以下操作(应该也说明了我要做什么),但是我遇到了递归错误:

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name== test :
            return 0.
        else:
            return self.__dict__[name]

>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp
最佳回答

你会遇到递归错误,因为在 __getattribute__ 内部尝试访问 self.__dict__ 属性会再次调用你的 __getattribute__。如果你使用 object__getattribute__,它就能正常工作:

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name== test :
            return 0.
        else:
            return object.__getattribute__(self, name)

这是因为object(在本例中)是基类。通过调用__getattribute__的基本版本,您可以避免之前的递归地狱。

Ipython输出与foo.py中的代码:

In [1]: from foo import *

In [2]: d = D()

In [3]: d.test
Out[3]: 0.0

In [4]: d.test2
Out[4]: 21

更新:

在当前文档的更多新式类的属性访问部分中,有一些内容建议这样做以避免无限递归。

问题回答

实际上,我相信你想使用__getattr__特殊方法。

来自Python文档的引用:

__getattr__( self, name) 的中文翻译为:__getattr__( self,name)

Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.
Note that if the attribute is found through the normal mechanism, __getattr__() is not called. (This is an intentional asymmetry between __getattr__() and __setattr__().) This is done both for efficiency reasons and because otherwise __setattr__() would have no way to access other attributes of the instance. Note that at least for instance variables, you can fake total control by not inserting any values in the instance attribute dictionary (but instead inserting them in another object). See the __getattribute__() method below for a way to actually get total control in new-style classes.

注意:为了使此功能正常工作,实例 不应 具有 test 属性,因此应删除 self.test=20 这一行。

Python语言参考:

In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.__getattribute__(self, name).

意义:

def __getattribute__(self,name):
    ...
        return self.__dict__[name]

你在调用一个叫做__dict__的属性。因为它是一个属性,__getattribute__会在查找__dict__时被调用,进而调用__getattribute__,接着......等等等等。

return  object.__getattribute__(self, name)

使用基类__getattribute__有助于找到真实的属性。

How is the __getattribute__ method used?

它在正常的点查询之前被称为。如果它引发AttributeError,那么我们调用__getattr__

使用此方法相对较少。在标准库中仅有两个定义:

$ grep -Erl  "def __getattribute__(self" cpython/Lib | grep -v "/test/"
cpython/Lib/_threading_local.py
cpython/Lib/importlib/util.py

最佳实践

以编程方式控制对单个属性的访问的正确方法是使用 property。类D 应该按以下方式编写(附带 setter 和 deleter 可以复制所需的行为):

class D(object):
    def __init__(self):
        self.test2=21

    @property
    def test(self):
        return 0.

    @test.setter
    def test(self, value):
           dummy function to avoid AttributeError on setting property   

    @test.deleter
    def test(self):
           dummy function to avoid AttributeError on deleting property   

和使用:

>>> o = D()
>>> o.test
0.0
>>> o.test =  foo 
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0

一种属性是数据描述符,因此它是正常点查找算法中首先查找的东西。

Options for __getattribute__

如果您绝对需要通过__getattribute__实现每个属性的查找,则有几个选项。

  • raise AttributeError, causing __getattr__ to be called (if implemented)
  • return something from it by
    • using super to call the parent (probably object s) implementation
    • calling __getattr__
    • implementing your own dotted lookup algorithm somehow

例如:

class NoisyAttributes(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self, name):
        print( getting:   + name)
        try:
            return super(NoisyAttributes, self).__getattribute__(name)
        except AttributeError:
            print( oh no, AttributeError caught and reraising )
            raise
    def __getattr__(self, name):
        """Called if __getattribute__ raises AttributeError"""
        return  close but no   + name    


>>> n = NoisyAttributes()
>>> nfoo = n.foo
getting: foo
oh no, AttributeError caught and reraising
>>> nfoo
 close but no foo 
>>> n.test
getting: test
20

What you originally wanted.

这个例子展示了你如何做到最初想要的事情:

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name== test :
            return 0.
        else:
            return super(D, self).__getattribute__(name)

而且会表现得像这样:

>>> o = D()
>>> o.test =  foo 
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
>>> del o.test

Traceback (most recent call last):
  File "<pyshell#216>", line 1, in <module>
    del o.test
AttributeError: test

Code review

Your code with comments. You have a dotted lookup on self in __getattribute__. This is why you get a recursion error. You could check if name is "__dict__" and use super to workaround, but that doesn t cover __slots__. I ll leave that as an exercise to the reader.

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name== test :
            return 0.
        else:      #   v--- Dotted lookup on self in __getattribute__
            return self.__dict__[name]

>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp

您确定要使用__getattribute__吗?您实际想要实现什么?

你所要求的最简单的方法是:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21

    test = 0

或者

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21

    @property
    def test(self):
        return 0

Edit: Note that an instance of D would have different values of test in each case. In the first case d.test would be 20, in the second it would be 0. I ll leave it to you to work out why.

Edit2: Greg pointed out that example 2 will fail because the property is read only and the __init__ method tried to set it to 20. A more complete example for that would be:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21

    _test = 0

    def get_test(self):
        return self._test

    def set_test(self, value):
        self._test = value

    test = property(get_test, set_test)

显然,作为一堂课程,这几乎没有用处,但它可以帮你得到一个继续学习的思路。

这是一个更可靠的版本:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21
    def __getattribute__(self, name):
        if name ==  test :
            return 0.
        else:
            return super(D, self).__getattribute__(name)

它调用了父类的__getattribute__方法,最终回落到对象的__getattribute__方法,如果其他祖先没有覆盖它。





相关问题
热门标签