如何在没有无限递归错误的情况下实现__getattribute__?

时间:2008-12-16 16:04:39

标签: python class oop recursion getattr

我想覆盖对类中一个变量的访问,但是通常会返回所有其他变量。如何使用__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

6 个答案:

答案 0 :(得分:108)

您收到递归错误,因为您尝试访问self.__dict__中的__getattribute__属性会再次调用__getattribute__。如果您改为使用object的{​​{1}},则可以使用:

__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的基本版本,你可以避免你以前的递归地狱。

使用foo.py中的代码输出Ipython:

__getattribute__

<强>更新

在当前文档中标题为More attribute access for new-style classes的部分中有一些内容,他们建议这样做以避免无限递归。

答案 1 :(得分:22)

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

来自Python文档的引用:

  

__getattr__( self, name)

     

当属性查找未在通常位置找到属性时调用(即,它不是实例属性,也不是在类树中找到自己)。 name是属性名称。此方法应返回(计算的)属性值或引发AttributeError异常   请注意,如果通过常规机制找到该属性,则不会调用__getattr__()。 (这是__getattr__()__setattr__()之间的故意不对称。)这是出于效率原因而做的,因为否则__setattr__()将无法访问实例的其他属性。请注意,至少对于实例变量,您可以通过不在实例属性字典中插入任何值来伪造总控制(而是将它们插入另一个对象中)。请参阅下面的__getattribute__()方法,了解在新式类中实际获得完全控制的方法。

注意:为了使其正常工作,实例应该具有test属性,因此应删除行self.test=20

答案 2 :(得分:15)

Python language reference:

  

为了避免无限递归   在这种方法中,它的实现   应始终调用基类   具有相同名称的方法来访问   例如,它需要的任何属性   object.__getattribute__(self, name)

含义:

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

您正在调用名为__dict__的属性。因为它是一个属性,所以__getattribute__会在搜索__dict__时调用__getattribute__来调用return object.__getattribute__(self, name) 来调用... yada yada yada

__getattribute__

使用基类{{1}}有助于找到真正的属性。

答案 3 :(得分:13)

您确定要使用__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

编辑: 请注意,D的实例在每种情况下都会有test的不同值。在第一种情况下d.test将是20,在第二种情况下它将是0.我将留给你解决原因。

EDIT2: Greg指出示例2将失败,因为该属性是只读的,并且__init__方法试图将其设置为20.更完整的示例将是:

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)

显然,作为一个班级,这几乎完全没用,但它让你有了继续前进的想法。

答案 4 :(得分:5)

这是一个更可靠的版本:

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 __ 方法,如果其他祖先不要覆盖它。

答案 5 :(得分:3)

  

如何使用__getattribute__方法?

在正常的点缀查找之前调用它。如果它引发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

属性是一个数据描述符,因此它是普通点查找算法中首先寻找的。

__getattribute__

的选项

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

  • 引发AttributeError,导致__getattr__被调用(如果已实施)
  • 从中返回一些东西
    • 使用super来调用父级(可能是object)实现
    • 致电__getattr__
    • 以某种方式实现您自己的点缀查找算法

例如:

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

你最初想要的是什么。

这个例子展示了你如何做你原本想要的事情:

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

代码审查

您的评论代码。您在__getattribute__中对自己进行了点缀查找。 这就是你得到递归错误的原因。您可以检查名称是否为"__dict__"并使用super进行解决,但这不包括__slots__。我将把它作为练习留给读者。

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