如何实现 __getattribute__ 而不出现无限递归错误?
- 2025-03-13 09:03:00
- admin 原创
- 83
问题描述:
我想覆盖对类中一个变量的访问,但正常返回所有其他变量。如何使用 来实现这一点__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
解决方案 1:
您会收到递归错误,因为您尝试访问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)
这是可行的,因为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
更新:
当前文档中标题为“新式类的更多属性访问”的部分中有一些内容,他们建议执行此操作以避免无限递归。
解决方案 2:
实际上,我相信您想使用__getattr__
特殊方法。
引用自 Python 文档:
__getattr__( self, name)
当属性查找未在通常的位置找到该属性时调用(即,它不是实例属性,也未在自身的类树中找到)。
name
是属性名称。此方法应返回(计算的)属性值或引发AttributeError
异常。请注意,如果通过正常机制找到该属性,
__getattr__()
则不会调用。(这是__getattr__()
和之间的故意不对称__setattr__()
。)这样做既出于效率原因,也因为否则__setattr__()
将无法访问实例的其他属性。请注意,至少对于实例变量,您可以通过不在实例属性字典中插入任何值(而是将它们插入另一个对象)来伪造完全控制。请参阅__getattribute__()
下面的方法,了解在新型类中实际获得完全控制的方法。
注意:为了使其正常工作,实例不应具有属性,因此应删除test
该行。self.test=20
解决方案 3:
Python 语言参考:
为了避免此方法中的无限递归,其实现应始终调用具有相同名称的基类方法来访问其所需的任何属性,例如
object.__getattribute__(self, name)
。
意义:
def __getattribute__(self,name):
...
return self.__dict__[name]
您正在调用一个名为 的属性__dict__
。因为它是一个属性,所以__getattribute__
在搜索__dict__
哪个调用__getattribute__
哪个调用时会被调用... 等等等等
return object.__getattribute__(self, name)
使用基类__getattribute__
有助于找到真正的属性。
解决方案 4:
该方法如何
__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__
。
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 进行了点状查找__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
解决方案 5:
您确定要使用 吗__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)
显然,作为一个类,这几乎完全没用,但它给你一个继续前进的想法。
解决方案 6:
这是一个更可靠的版本:
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 __方法。
扫码咨询,免费领取项目管理大礼包!