【问题标题】:How do I implement __getattribute__ without an infinite recursion error?如何在没有无限递归错误的情况下实现 __getattribute__?
【发布时间】:2010-09-27 04:05:48
【问题描述】:

我想覆盖对类中一个变量的访问,但正常返回所有其他变量。如何使用__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

【问题讨论】:

    标签: python class getattr


    【解决方案1】:

    您会收到递归错误,因为您尝试访问 __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
    

    更新:

    在当前文档中标题为More attribute access for new-style classes 的部分中有一些内容,他们建议这样做以避免无限递归。

    【讨论】:

    • 有趣。那你在那里做什么?为什么对象会有我的变量?
    • ..我想总是使用对象吗?如果我从其他类继承怎么办?
    • 是的,每次你创建一个类而不写你自己的,你使用object提供的getattribute
    • 使用 super() 并因此使用 python 在您的基类中找到的第一个 getattribute 方法不是更好吗? - super(D, self).__getattribute__(name)
    • 或者你可以在 Python 3 中使用super().__getattribute__(name)
    【解决方案2】:

    __getattribute__ 方法是如何使用的?

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

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

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

    最佳实践

    以编程方式控制对单个属性的访问的正确方法是使用propertyD 类应该写成如下(设置器和删除器可选地复制明显的预期行为):

    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
    

    代码审查

    您的带有 cmets 的代码。您在__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
    

    【讨论】:

      【解决方案3】:

      Python language reference:

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

      意思:

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

      您正在调用一个名为__dict__ 的属性。因为它是一个属性,所以__getattribute__ 被调用以搜索__dict__,它调用__getattribute__,它调用... yada yada yada

      return  object.__getattribute__(self, name)
      

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

      【讨论】:

        【解决方案4】:

        您确定要使用__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)
        

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

        【讨论】:

        • 哦,当你运行课程时,这并不安静,不是吗? init 中的文件“Script1.py”第 5 行 self.test = 20 AttributeError: can't set attribute
        • 是的。我将把它作为第三个例子来解决。很好发现。
        【解决方案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__方法,如果其他祖先不覆盖它。

        【讨论】:

          【解决方案6】:

          其实我相信你想改用__getattr__这个特殊方法。

          引用自 Python 文档:

          __getattr__( self, name)

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

          注意:要使其正常工作,实例应该具有test 属性,因此应删除self.test=20 行。

          【讨论】:

          猜你喜欢
          • 2021-06-09
          • 1970-01-01
          • 2012-09-14
          • 1970-01-01
          • 2019-05-25
          • 1970-01-01
          • 2010-09-09
          • 1970-01-01
          相关资源
          最近更新 更多