如何实现 __getattribute__ 而不出现无限递归错误?

2025-03-13 09:03:00
admin
原创
82
摘要:问题描述:我想覆盖对类中一个变量的访问,但正常返回所有其他变量。如何使用 来实现这一点__getattribute__?我尝试了以下操作(这也应该说明我正在尝试做什么)但是我收到递归错误:class D(object): def __init__(self): self.test=20...

问题描述:

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

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   4046  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2770  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   90  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   105  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   93  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用