如何使用__getattribute__方法?

我想覆盖对一个类中的一个variables的访问,但通常返回所有其他的variables。 我如何用__getattribute__完成这个工作?

我试过以下(这也应该说明我在做什么),但我得到一个recursion错误:

 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 

你得到一个recursion错误,因为你调用相同的函数,你的__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__的基本版本,您可以避免之前的recursion式地狱。

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

更新:

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

其实,我相信你想要使用__getattr__特殊的方法。

从Python文档引用:

__getattr__( self, name)

当一个属性查找没有在通常的地方find属性(即它不是一个实例属性,也不是在自己的类树中find)时调用。 名称是属性名称。 此方法应该返回(计算)的属性值或引发AttributeErrorexception。
请注意,如果通过正常机制find该属性,则不会调用__getattr__() 。 (这是__getattr__()__setattr__()之间的故意不对称__setattr__() 。这是出于效率的原因,否则__setattr__()将无法访问实例的其他属性。 请注意,至less在实例variables中,通过在实例属性字典中不插入任何值(而是将其插入到另一个对象中),可以伪造完全控制权。 请参阅下面的__getattribute__()方法,以实际获得新风格类的完全控制权。

注意:为了这个工作,实例不应该有一个test属性,所以行self.test=20应该被删除。

Python语言参考:

为了避免在这个方法中的无限recursion,它的实现应该总是调用具有相同名字的基类方法来访问它需要的任何属性,例如object.__getattribute__(self, name)

含义:

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

您正在调用一个名为__dict__的属性。 因为它是一个属性, __getattribute__被调用search__dict__调用__getattribute__调用… yada yada yada

 return object.__getattribute__(self, name) 

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

你确定要使用__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:格雷格指出,例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) 

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

这是一个更可靠的版本:

 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 __方法,如果其他祖先不覆盖它。

如何使用__getattribute__方法?

它在正常的点状查找之前被调用。 如果引发AttributeError ,那么我们调用__getattr__

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

 $ grep -Erl "def __getattribute__\(self" cpython/Lib | grep -v "/test/" cpython/Lib/_threading_local.py cpython/Lib/importlib/util.py 

最佳实践

以编程方式控制对单个属性的访问的正确方法是使用propertyD类应如下编写(使用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 

一个属性是一个数据描述符,因此它是在正常的虚线查找algorithm中查找的第一个东西。

__getattribute__选项

如果您绝对需要通过__getattribute__实现每个属性的查找,您可以使用几个选项。

  • 引发AttributeError ,导致__getattr__被调用(如果实现)
  • 从中返回一些东西
    • 使用super来调用父(可能是object )的实现
    • 调用__getattr__
    • 以某种方式实现您自己的虚线查找algorithm

例如:

 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__有自己的点状查询。 这就是为什么你得到recursion错误。 您可以检查名称是否是"__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