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

时间:2022-11-28 09:39:49

I want to override access to one variable in a class, but return all others normally. How do I accomplish this with __getattribute__?

我想重写对类中的一个变量的访问,但通常返回所有其他变量。我如何用__getattribute__来实现这一点?

I tried the following (which should also illustrate what I'm trying to do) but I get a recursion error:

我尝试了以下方法(这也说明了我要做的事情),但是我得到了一个递归错误:

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

6 个解决方案

#1


93  

You get a recursion error because your attempt to access the self.__dict__ attribute inside __getattribute__ invokes your __getattribute__ again. If you use object's __getattribute__ instead, it works:

您会得到一个递归错误,因为您试图访问self。__getattribute__中的__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)

This works because object (in this example) is the base class. By calling the base version of __getattribute__ you avoid the recursive hell you were in before.

这样做是因为对象(在本例中)是基类。通过调用__getattribute__的基本版本,您可以避免以前所处的递归地狱。

Ipython output with code in foo.py:

Ipython输出,代码为页脚。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

Update:

更新:

There's something in the section titled More attribute access for new-style classes in the current documentation, where they recommend doing exactly this to avoid the infinite recursion.

在当前文档中,有一节题为“为新样式类提供更多的属性访问”,他们建议这样做以避免无限递归。

#2


21  

Actually, I believe you want to use the __getattr__ special method instead.

实际上,我认为您应该使用__getattr__特殊方法。

Quote from the Python docs:

引用Python文档:

__getattr__( self, name)

__getattr__(自我,名称)

Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.
Note that if the attribute is found through the normal mechanism, __getattr__() is not called. (This is an intentional asymmetry between __getattr__() and __setattr__().) This is done both for efficiency reasons and because otherwise __setattr__() would have no way to access other attributes of the instance. Note that at least for instance variables, you can fake total control by not inserting any values in the instance attribute dictionary (but instead inserting them in another object). See the __getattribute__() method below for a way to actually get total control in new-style classes.

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

Note: for this to work, the instance should not have a test attribute, so the line self.test=20 should be removed.

注意:要使其工作,实例不应该有测试属性,所以是line self。测试= 20应该被删除。

#3


15  

Python language reference:

Python语言参考:

In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.__getattribute__(self, name).

为了避免在此方法中出现无限递归,它的实现应该始终调用具有相同名称的基类方法来访问它需要的任何属性,例如对象。__getattribute__(自我、名称)。

Meaning:

意义:

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

You're calling for an attribute called __dict__. Because it's an attribute, __getattribute__ gets called in search for __dict__ which calls __getattribute__ which calls ... yada yada yada

您需要一个名为__dict__的属性。因为它是一个属性,__getattribute__在搜索__dict__时被调用,__getattribute__调用。雅达雅达雅达

return  object.__getattribute__(self, name)

Using the base classes __getattribute__ helps finding the real attribute.

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

#4


13  

Are you sure you want to use __getattribute__? What are you actually trying to achieve?

您确定要使用__getattribute__吗?你真正想要达到的目标是什么?

The easiest way to do what you ask is:

最简单的方法就是:

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

    test = 0

or:

或者:

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

    @property
    def test(self):
        return 0

Edit: Note that an instance of D would have different values of test in each case. In the first case d.test would be 20, in the second it would be 0. I'll leave it to you to work out why.

编辑:注意,D的实例在每种情况下都有不同的测试值。第一种情况是d。测试是20,第二个是0。我把问题留给你来解决。

Edit2: Greg pointed out that example 2 will fail because the property is read only and the __init__ method tried to set it to 20. A more complete example for that would be:

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)

Obviously, as a class this is almost entirely useless, but it gives you an idea to move on from.

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

#5


5  

Here is a more reliable version:

这里有一个更可靠的版本:

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)

It calls __getattribute__ method from parent class, eventually falling back to object.__getattribute__ method if other ancestors don't override it.

它从父类调用__getattribute__方法,最终返回到对象。__getattribute__方法如果其他祖先不重写它。

#6


1  

How is the __getattribute__ method used?

It is called before the normal dotted lookup. If it raises AttributeError, then we call __getattr__.

它在普通的点查找之前被调用。如果它引发了AttributeError,那么我们调用__getattr__。

Use of this method is rather rare. There are only two definitions in the standard library:

这种方法很少见。在标准库中只有两个定义:

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

Best Practice

最佳实践

The proper way to programmatically control access to a single attribute is with property. Class D should be written as follows (with the setter and deleter optionally to replicate apparent intended behavior):

通过编程控制对单个属性的访问的正确方法是使用属性。类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'''

And usage:

和用法:

>>> o = D()
>>> o.test
0.0
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0

A property is a data descriptor, thus it is the first thing looked for in the normal dotted lookup algorithm.

属性是数据描述符,因此它是在普通点查找算法中查找的第一个对象。

Options for __getattribute__

You several options if you absolutely need to implement lookup for every attribute via __getattribute__.

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

  • raise AttributeError, causing __getattr__ to be called (if implemented)
  • 提高AttributeError,导致__getattr__被调用(如果实现)
  • return something from it by
    • using super to call the parent (probably object's) implementation
    • 使用super调用父(可能是对象)实现
    • calling __getattr__
    • 调用__getattr__
    • implementing your own dotted lookup algorithm somehow
    • 实现您自己的点查找算法
  • 通过使用super调用父(可能是对象的)实现调用__getattr__以实现您自己的点查找算法来返回一些东西

For example:

例如:

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

What you originally wanted.

And this example shows how you might do what you originally wanted:

这个例子展示了你如何做你最初想做的事情:

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)

And will behave like this:

这样做:

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

Code review

Your code with comments. You have a dotted lookup on self in __getattribute__. This is why you get a recursion error. You could check if name is "__dict__" and use super to workaround, but that doesn't cover __slots__. I'll leave that as an exercise to the reader.

代码和注释。在__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

#1


93  

You get a recursion error because your attempt to access the self.__dict__ attribute inside __getattribute__ invokes your __getattribute__ again. If you use object's __getattribute__ instead, it works:

您会得到一个递归错误,因为您试图访问self。__getattribute__中的__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)

This works because object (in this example) is the base class. By calling the base version of __getattribute__ you avoid the recursive hell you were in before.

这样做是因为对象(在本例中)是基类。通过调用__getattribute__的基本版本,您可以避免以前所处的递归地狱。

Ipython output with code in foo.py:

Ipython输出,代码为页脚。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

Update:

更新:

There's something in the section titled More attribute access for new-style classes in the current documentation, where they recommend doing exactly this to avoid the infinite recursion.

在当前文档中,有一节题为“为新样式类提供更多的属性访问”,他们建议这样做以避免无限递归。

#2


21  

Actually, I believe you want to use the __getattr__ special method instead.

实际上,我认为您应该使用__getattr__特殊方法。

Quote from the Python docs:

引用Python文档:

__getattr__( self, name)

__getattr__(自我,名称)

Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.
Note that if the attribute is found through the normal mechanism, __getattr__() is not called. (This is an intentional asymmetry between __getattr__() and __setattr__().) This is done both for efficiency reasons and because otherwise __setattr__() would have no way to access other attributes of the instance. Note that at least for instance variables, you can fake total control by not inserting any values in the instance attribute dictionary (but instead inserting them in another object). See the __getattribute__() method below for a way to actually get total control in new-style classes.

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

Note: for this to work, the instance should not have a test attribute, so the line self.test=20 should be removed.

注意:要使其工作,实例不应该有测试属性,所以是line self。测试= 20应该被删除。

#3


15  

Python language reference:

Python语言参考:

In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.__getattribute__(self, name).

为了避免在此方法中出现无限递归,它的实现应该始终调用具有相同名称的基类方法来访问它需要的任何属性,例如对象。__getattribute__(自我、名称)。

Meaning:

意义:

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

You're calling for an attribute called __dict__. Because it's an attribute, __getattribute__ gets called in search for __dict__ which calls __getattribute__ which calls ... yada yada yada

您需要一个名为__dict__的属性。因为它是一个属性,__getattribute__在搜索__dict__时被调用,__getattribute__调用。雅达雅达雅达

return  object.__getattribute__(self, name)

Using the base classes __getattribute__ helps finding the real attribute.

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

#4


13  

Are you sure you want to use __getattribute__? What are you actually trying to achieve?

您确定要使用__getattribute__吗?你真正想要达到的目标是什么?

The easiest way to do what you ask is:

最简单的方法就是:

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

    test = 0

or:

或者:

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

    @property
    def test(self):
        return 0

Edit: Note that an instance of D would have different values of test in each case. In the first case d.test would be 20, in the second it would be 0. I'll leave it to you to work out why.

编辑:注意,D的实例在每种情况下都有不同的测试值。第一种情况是d。测试是20,第二个是0。我把问题留给你来解决。

Edit2: Greg pointed out that example 2 will fail because the property is read only and the __init__ method tried to set it to 20. A more complete example for that would be:

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)

Obviously, as a class this is almost entirely useless, but it gives you an idea to move on from.

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

#5


5  

Here is a more reliable version:

这里有一个更可靠的版本:

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)

It calls __getattribute__ method from parent class, eventually falling back to object.__getattribute__ method if other ancestors don't override it.

它从父类调用__getattribute__方法,最终返回到对象。__getattribute__方法如果其他祖先不重写它。

#6


1  

How is the __getattribute__ method used?

It is called before the normal dotted lookup. If it raises AttributeError, then we call __getattr__.

它在普通的点查找之前被调用。如果它引发了AttributeError,那么我们调用__getattr__。

Use of this method is rather rare. There are only two definitions in the standard library:

这种方法很少见。在标准库中只有两个定义:

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

Best Practice

最佳实践

The proper way to programmatically control access to a single attribute is with property. Class D should be written as follows (with the setter and deleter optionally to replicate apparent intended behavior):

通过编程控制对单个属性的访问的正确方法是使用属性。类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'''

And usage:

和用法:

>>> o = D()
>>> o.test
0.0
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0

A property is a data descriptor, thus it is the first thing looked for in the normal dotted lookup algorithm.

属性是数据描述符,因此它是在普通点查找算法中查找的第一个对象。

Options for __getattribute__

You several options if you absolutely need to implement lookup for every attribute via __getattribute__.

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

  • raise AttributeError, causing __getattr__ to be called (if implemented)
  • 提高AttributeError,导致__getattr__被调用(如果实现)
  • return something from it by
    • using super to call the parent (probably object's) implementation
    • 使用super调用父(可能是对象)实现
    • calling __getattr__
    • 调用__getattr__
    • implementing your own dotted lookup algorithm somehow
    • 实现您自己的点查找算法
  • 通过使用super调用父(可能是对象的)实现调用__getattr__以实现您自己的点查找算法来返回一些东西

For example:

例如:

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

What you originally wanted.

And this example shows how you might do what you originally wanted:

这个例子展示了你如何做你最初想做的事情:

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)

And will behave like this:

这样做:

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

Code review

Your code with comments. You have a dotted lookup on self in __getattribute__. This is why you get a recursion error. You could check if name is "__dict__" and use super to workaround, but that doesn't cover __slots__. I'll leave that as an exercise to the reader.

代码和注释。在__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