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

我希望重写对类中一个变量的访问,但通常返回所有其他变量。如何使用 __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
120899 次浏览

您得到一个递归错误,因为您尝试访问 __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__的基本版本,您可以避免以前所处的递归地狱。

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

更新:

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

您确定要使用 __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。我会让你来找出原因。

编辑2: 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)

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

Python 语言参考:

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

意思是:

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

您正在调用一个名为 __dict__的属性。因为它是一个属性,__getattribute__被调用来搜索 __dict____dict__调用 __getattribute____getattribute__调用... 等等等等

return  object.__getattribute__(self, name)

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

实际上,我相信您想要使用 __getattr__特殊方法来代替。

引自 Python 文档:

__getattr__( self, name)

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

注意: 要实现这一点,实例应该具有 test属性,因此应该删除 self.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)

它从父类调用 _ _方法,如果其他祖先没有重写它,则最终回到 object._ _方法。

如何使用 __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 和 delete 可选地复制明显的预期行为) :

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__中有一个关于 self 的点状查找。 这就是为什么会出现递归错误。您可以检查名称是否为 "__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