Python中如何让__setattr__与__getattr__排除指定属性?
Config类重写__setattr__/__getattr__引发无限递归的解决方法
问题背景
想要实现一个Config类,用来存储和跟踪任意Setting对象,支持像普通属性一样获取和设置(如c.x = 1; print(c.foo),其中x、foo是初始化传入的Setting的name属性)。重写__setattr__和__getattr__后出现无限递归报错。
原代码
#config.py class Setting: def __init__(self, name, value): self.name = name self.value = value def __repr__(self) -> str: return f"Setting(name: {self.name}, value: {self.value})" class Config: #_settings = {} def __init__(self, settings): self._settings = settings def __setattr__(self, name, value): try: self._settings[name].value = value except KeyError as err: try: return super(Config, self).__setattr__(name, value) except AttributeError: raise err def __getattr__(self, name): try: return self._settings[name].value except KeyError as err: try: return super(Config, self).__getattribute__(name) except AttributeError: raise err class Interface: def __init__(self): settings = { s.name: s for s in [ Setting("x", 1.), Setting("y", 2.), ] } self.config = Config(settings) if __name__ == "__main__": i = Interface() print(i.config._settings) i.config.x = 9. print(i.config._settings) #print(Config._settings)
报错信息
$ python config.py Traceback (most recent call last): File "config.py", line 52, in <module> i = Interface() File "config.py", line 48, in __init__ self.config = Config(settings) File "config.py", line 15, in __init__ self._settings = settings File "config.py", line 20, in __setattr__ self._settings[name].value = value File "config.py", line 31, in __getattr__ return self._settings[name].value File "config.py", line 31, in __getattr__ return self._settings[name].value File "config.py", line 31, in __getattr__ return self._settings[name].value [Previous line repeated 991 more times] RecursionError: maximum recursion depth exceeded
核心原因
在Config.__init__中执行self._settings = settings时,会触发重写的__setattr__方法;而__setattr__里尝试访问self._settings,此时该属性尚未被初始化,会触发__getattr__;__getattr__又试图访问self._settings,进而形成无限递归。
可行解决方案
方案1:初始化时直接调用父类__setattr__设置_settings
修改Config的构造方法,跳过自定义的__setattr__逻辑,直接用父类方法初始化_settings:
class Config: def __init__(self, settings): # 调用父类方法直接设置,避免触发自定义__setattr__ super().__setattr__('_settings', settings) def __setattr__(self, name, value): try: self._settings[name].value = value except KeyError as err: try: return super().__setattr__(name, value) except AttributeError: raise err def __getattr__(self, name): try: return self._settings[name].value except KeyError as err: try: return super().__getattribute__(name) except AttributeError: raise err
方案2:在__setattr__和__getattr__中优先处理_settings属性
在两个方法开头判断属性名是否为_settings,如果是则直接用父类方法处理,避免进入递归逻辑:
class Config: def __init__(self, settings): self._settings = settings def __setattr__(self, name, value): # 优先处理_settings属性,直接调用父类方法 if name == '_settings': return super().__setattr__(name, value) try: self._settings[name].value = value except KeyError as err: try: return super().__setattr__(name, value) except AttributeError: raise err def __getattr__(self, name): # 优先处理_settings属性,直接调用父类方法 if name == '_settings': return super().__getattribute__(name) try: return self._settings[name].value except KeyError as err: try: return super().__getattribute__(name) except AttributeError: raise err
方案3:直接操作实例的__dict__(不推荐)
初始化时直接修改实例的__dict__字典,绕过__setattr__方法:
class Config: def __init__(self, settings): self.__dict__['_settings'] = settings # 其余方法保持不变
验证效果
修改后运行测试代码,能够正常输出_settings的内容,且设置i.config.x = 9.后,对应的Setting对象的value会被正确更新,不再出现递归报错。
内容的提问来源于stack exchange,提问作者nieswand
相关产品推荐
相关产品推荐

