【问题标题】:Avoiding infinite loops in __getattribute__ [duplicate]避免 __getattribute__ 中的无限循环 [重复]
【发布时间】:2012-11-24 04:39:05
【问题描述】:

方法__getattribute__需要仔细编写,以避免死循环。例如:

class A:
    def __init__(self):
        self.x = 100

    def __getattribute__(self, x):
        return self.x

>>> a = A()
>>> a.x    # infinite looop
RuntimeError: maximum recursion depth exceeded while calling a Python object


class B:
    def __init__(self):
        self.x = 100

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

>>> b = B()
>>> b.x    # infinite looop
RuntimeError: maximum recursion depth exceeded while calling a Python object

因此我们需要这样写方法:

class C:
    def __init__(self):
        self.x = 100

    def __getattribute__(self, x):
        # 1. error
        # AttributeError: type object 'object' has no attribute '__getattr__'
        # return object.__getattr__(self, x)
        
        # 2. works
        return object.__getattribute__(self, x)
        
        # 3. works too
        # return super().__getattribute__(x)

我的问题是为什么object.__getattribute__ 方法有效? object 从哪里得到__getattribute__ 方法?如果object 没有任何__getattribute__,那么我们只是在类C 上调用相同的方法,但通过超类。为什么,那么通过超类调用方法不会导致死循环?

【问题讨论】:

  • 您确定需要__getattribute__ 而不是__getattr__
  • 是的,我是,因为我需要拦截所有在我的类中获取的属性。但即使我没有,我仍然想知道为什么会这样的全部细节。
  • 好吧,要么你必须拦截 all 属性访问,要么你不需要:-)
  • @MartijnPieters——我同意。我的主要困惑是为什么从 object 调用 SAME 方法不会导致无限递归。
  • 为什么说“对象没有__getattribute__”? a = object(); dir(a) 你会看到它列出来...

标签: python python-3.x


【解决方案1】:

您似乎认为__getattribute__ 的实现只是一个钩子,如果您提供它,Python 将调用它,否则解释器将直接执行其正常的魔法。

这是不正确的。当python在实例上查找属性时,__getattribute__是所有属性访问的主要入口,object提供默认实现。因此,您的实现覆盖原来的,如果您的实现没有提供返回属性的替代方法,它就会失败。您不能在该方法中使用属性访问,因为对实例 (self) 的所有属性访问都会再次通过 type(self).__getattribute__(self, attr) 进行引导。

解决此问题的最佳方法是再次调用被覆盖的原始文件。这就是super(C, self).__getattribute__(attr) 的用武之地;您要求按类解析顺序的下一个类为您处理属性访问。

或者,您可以直接调用未绑定的object.__getattribute__() 方法。此方法的 C 实现是属性访问的最后一站(它可以直接访问__dict__,因此不受相同限制)。

请注意,super() 返回一个代理对象,该对象将在方法解析有序基类中查找接下来可以找到的任何方法。如果不存在这样的方法,它将因属性错误而失败。它从不调用原始方法。因此,Foo.bar() 查找 super(Foo, self).bar 将是基类实现或属性错误,永远不会 Foo.bar 本身。

【讨论】:

    【解决方案2】:

    当你这样做时:

        return object.__getattribute__(self, x)
    

    您正在调用一个特定的函数——对象类中定义的函数,而不是 A 中定义的函数,因此没有递归。

    当你这样做时:

        return self.x
    

    你让python选择调用哪个函数,它从A调用那个函数,你有一个无限递归。

    【讨论】:

    • object 类上没有定义这样的函数。如果在那里定义了一个函数,那么object 也将定义__getattr__(正如#1 错误所示,它没有定义)。那么,为什么对象会只定义__getattribute__ 方法呢?
    • @greengit:为什么不呢?当我访问object.__getattribute__ 时,我看到了<slot wrapper '__getattribute__' of 'object' objects>
    • 所以你是说object 只定义__getattribte__ 而不是__getattr__?据我所知,object 类对所有这些方法的实现都是无操作的。如果我在某处错了,请纠正我。
    • @greengit:不,object.__getattribute__ 是对象属性访问的主要访问点。当您在子类上定义它时,您将覆盖它并再次获得 normal 行为,您需要调用覆盖的实现。
    • “我认为对象有无操作实现”?世界上为什么……?如果根对象的特殊方法的实现是无操作的,事情会如何运作?当你自己写的时候,你会覆盖什么?如果基本方法没有做任何事情,这些覆盖将如何访问基本功能?这甚至没有意义。
    【解决方案3】:

    这样写(C继承自object):

    class C(object):
       def __init__(self):
           self.x = 100
    
       def __getattribute__(self, x):
           return object.__getattribute__(self, x)
    

    现在您明白为什么 object.__getattribute__(self, x) 有效 - 您正在调用父对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-01
      • 2022-01-15
      • 1970-01-01
      • 2015-05-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多