【问题标题】:How to use __setattr__ correctly, avoiding infinite recursion如何正确使用__setattr__,避免无限递归
【发布时间】:2013-06-05 21:16:34
【问题描述】:

我想定义一个包含readwrite方法的类,可以这样调用:

instance.read
instance.write
instance.device.read
instance.device.write

为了不使用交错类,我的想法是覆盖__getattr____setattr__ 方法并检查给定名称是否为device 以将返回重定向到self。但是我遇到了一个无限递归的问题。示例代码如下:

class MyTest(object):
    def __init__(self, x):
        self.x = x

    def __setattr__(self, name, value):
        if name=="device":
            print "device test"
        else:
            setattr(self, name, value)

test = MyTest(1)

__init__ 中,代码试图创建一个新属性x,它调用__setattr__,它再次调用__setattr__ 等等。我需要如何更改此代码,在这种情况下,会创建 self 的新属性 x,并保存值 1

或者有没有更好的方法来处理像 instance.device.read 这样被“映射”到 instance.read 的调用?

因为总是有关于为什么的问题:我需要创建 xmlrpc 调用的抽象,可以创建像 myxmlrpc.instance,device.read 和类似的非常简单的方法。我需要“模拟”它以模仿这种多点方法调用。

【问题讨论】:

  • 使用@property 可以更轻松地解决此用例

标签: python python-2.7 getattr setattr


【解决方案1】:

必须调用父类__setattr__方法:

class MyTest(object):

    def __init__(self, x):
        self.x = x

    def __setattr__(self, name, value):
        if name=="device":
            print "device test"
        else:
            super(MyTest, self).__setattr__(name, value)
            # in python3+ you can omit the arguments to super:
            #super().__setattr__(name, value)

关于最佳实践,既然您打算通过 xml-rpc 使用它,我认为在 _dispatch 方法中这样做可能会更好。

一种快速而肮脏的方法是简单地做:

class My(object):
    def __init__(self):
        self.device = self

【讨论】:

  • 我喜欢快速而简单的方式:只需一行即可获得所需的行为!谢谢
  • 这在Python docs中也有提及。
  • @Bakuriu 你能解释一下这条线到底在做什么吗? super(MyTest, self).__setattr__(name, value)
  • @Shahryar 我的回答的第一行对此进行了解释。它正在调用父类__setattr__ 方法。 super(Class, instance) 创建一个对象,该对象能够调用instance 上的Class 父类的方法。在这种情况下,您也可以使用object.__setattr__(instance, name, value),但super 能够正确处理多继承,应该是首选。
  • @Shahryar 因为那甚至不是有效的python语法。 self[name] = value 等价于self.__setitem__(name, value),大部分时间都没有实现。如果你尝试使用内置的 setattr 会发生什么,setattr(self, name value) 将递归调用 __setattr__,你最终会陷入无限循环。
【解决方案2】:

或者你可以从__setattr__()内部修改self.__dict__

class SomeClass(object):

    def __setattr__(self, name, value):
        print(name, value)
        self.__dict__[name] = value

    def __init__(self, attr1, attr2):
        self.attr1 = attr1
        self.attr2 = attr2


sc = SomeClass(attr1=1, attr2=2)

sc.attr1 = 3

【讨论】:

  • 这个为什么不调用setattr(self, "__dict__", value),重复原来的无限递归问题?
  • 因为这不是设置__dict__属性,所以先获取它,然后在返回的dict上设置一个项目,即它正在调用类似:getattr(self, "__dict__").__setitem__(name, value)
【解决方案3】:

你也可以使用object

class TestClass:
    def __init__(self):
            self.data = 'data'
    def __setattr__(self, name, value):
            print("Attempt to edit the attribute %s" %(name))
            object.__setattr__(self, name, value)

【讨论】:

  • 这是一个内置类。所有类都继承自这个类(甚至__setattr____getattr____dict__、...)
  • 类似的type then ?
  • 不完全相似,type(TestClass) == objecttype == object 返回 False。请注意,所有内容都有 __setattr__ 方法,甚至是字符串:"a".__setattr__ 因为所有类都继承自内置类 object 甚至是字符串
【解决方案4】:

或者你可以只使用@property:

class MyTest(object):

    def __init__(self, x):
        self.x = x

    @property
    def device(self):
        return self

【讨论】:

    【解决方案5】:

    如果你不想指定哪些属性可以设置或不能设置,你可以拆分类来延迟get/set钩子直到初始化之后:

    class MyTest(object):
        def __init__(self, x):
            self.x = x
            self.__class__ = _MyTestWithHooks
    
    class _MyTestWithHooks(MyTest):
        def __setattr__(self, name, value):
            ...
        def __getattr__(self, name):
            ...
    
    if __name__ == '__main__':
        a = MyTest(12)
        ...
    

    如代码中所述,您需要实例化 MyTest,因为实例化 _MyTestWithHooks 将导致与之前相同的无限递归问题。

    【讨论】:

      猜你喜欢
      • 2020-11-10
      • 1970-01-01
      • 1970-01-01
      • 2013-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-31
      相关资源
      最近更新 更多