我想覆盖对类中一个变量的访问,但是通常会返回所有其他变量。如何使用__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
答案 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)
为了避免无限递归 在这种方法中,它的实现 应始终调用基类 具有相同名称的方法来访问 例如,它需要的任何属性
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
属性是一个数据描述符,因此它是普通点查找算法中首先寻找的。 p>
__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