119

我想覆盖对类中一个变量的访问,但正常返回所有其他变量。我如何做到这一点__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
4

6 回答 6

138

您会收到递归错误,因为您尝试访问self.__dict__内部属性会再次__getattribute__调用您__getattribute__。如果您使用object's__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__你可以避免你之前遇到的递归地狱。

foo.py 中带有代码的 Ipython 输出:

In [1]: from foo import *

In [2]: d = D()

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

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

更新:

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

于 2008-12-16T16:26:37.387 回答
32

实际上,我相信您想改用__getattr__特殊方法。

引用 Python 文档:

__getattr__( self, name)

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

注意:为此,实例不应具有属性test,因此self.test=20应删除该行。

于 2008-12-16T22:01:38.053 回答
20

Python语言参考:

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

意义:

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

您正在调用一个名为__dict__. 因为它是一个属性,所以__getattribute__被调用以搜索__dict__哪个调用__getattribute__哪个调用... yada yada yada

return  object.__getattribute__(self, name)

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

于 2008-12-16T16:39:23.710 回答
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)

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

于 2008-12-16T16:32:36.267 回答
11

方法是如何__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应该写成如下(设置器和删除器可选地复制明显的预期行为):

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

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

代码审查

您的代码带有注释。您对 self in 进行了虚线查找__getattribute__。这就是你得到递归错误的原因。您可以检查 name 是否为"__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
于 2015-02-05T23:48:48.703 回答
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 __方法,如果其他祖先不覆盖它。

于 2013-05-29T10:18:19.013 回答