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

Gre*_*reg 89 python oop recursion class 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
Run Code Online (Sandbox Code Playgroud)

Egi*_*gil 115

您收到递归错误,因为您尝试访问self.__dict__内部属性会再次__getattribute__调用您__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)
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为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
Run Code Online (Sandbox Code Playgroud)

更新:

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

  • 使用super()是不是更好,因此在你的基类中使用python发现的第一个__getattribute__方法? - `super(D,self).__ getattribute __(name)` (17认同)
  • 或者你可以在Python 3中使用`super().__ getattribute __(name)`. (9认同)

tzo*_*zot 24

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

引用Python文档:

__getattr__( self, name)

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

注意:这个工作,实例应该不会有一个test属性,因此行self.test=20应该被删除.

  • 实际上,根据OP代码的性质,为`test`覆盖`__getattr__`将毫无用处,因为它总是会在"通常的地方"找到它. (2认同)

tte*_*sse 16

Python语言参考:

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

含义:

def __getattribute__(self,name):
    ...
        return self.__dict__[name]
Run Code Online (Sandbox Code Playgroud)

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

return  object.__getattribute__(self, name)
Run Code Online (Sandbox Code Playgroud)

使用基类__getattribute__有助于查找真实属性.


Sin*_*ned 13

你确定要使用__getattribute__吗?你究竟想要实现什么目标?

做你问的最简单的方法是:

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

    test = 0
Run Code Online (Sandbox Code Playgroud)

要么:

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

    @property
    def test(self):
        return 0
Run Code Online (Sandbox Code Playgroud)

编辑:请注意,在每种情况下,实例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)
Run Code Online (Sandbox Code Playgroud)

显然,作为一个班级,这几乎完全无用,但它给你一个继续前进的想法.


Aar*_*all 10

__getattribute__方法是如何使用的?

它在正常的虚线查找之前被调用。如果它加注AttributeError,那么我们跟注__getattr__

这种方法的使用相当少见。标准库中只有两个定义:

$ grep -Erl  "def __getattribute__\(self" cpython/Lib | grep -v "/test/"
cpython/Lib/_threading_local.py
cpython/Lib/importlib/util.py
Run Code Online (Sandbox Code Playgroud)

最佳实践

以编程方式控制对单个属性的访问的正确方法是使用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'''
Run Code Online (Sandbox Code Playgroud)

和用法:

>>> o = D()
>>> o.test
0.0
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
Run Code Online (Sandbox Code Playgroud)

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

选项 __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
Run Code Online (Sandbox Code Playgroud)

你最初想要的。

这个例子展示了你可以如何做你最初想要的:

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)
Run Code Online (Sandbox Code Playgroud)

并且会像这样:

>>> 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
Run Code Online (Sandbox Code Playgroud)

代码审查

您的代码带有注释。您对 self 进行了虚线查找__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
Run Code Online (Sandbox Code Playgroud)


Elm*_*lmo 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)
Run Code Online (Sandbox Code Playgroud)

它从父类调用__ getattribute __方法,最终回退到object.__ getattribute __方法,如果其他祖先不覆盖它.