You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

整合dict、defaultdict与SimpleNamespace的自定义类遇无限递归错误

解决fluiddict类中__getattr__/__setattr__的无限递归问题

我正在编写一个整合dict、defaultdict和SimpleNamespace功能的Python类,目前已有如下代码:

import warnings

class fluiddict:
    """!    A class that emulates a dictionary, while also being able to support attribute assignment and default values.
            The default value of `default_factory` is None. This means a KeyError will be raised when non-existent data is requested
            To specify a default value of None, use `default_factory=lambda key: None`
    """

    def __contains__(self, key):
        return key in self.datastore

    def __getitem__(self,key):
        if self.raise_KeyError and key not in self.datastore:
            raise KeyError(f"Key '{key}' was not found in the datastore and no default factory was provided.")

        if key not in self.datastore:
            try:
                return self.default_factory(key)
            except Exception as e:
                print("An unknown exception occured while trying to provide a default value. Is your default factory valid?")
                raise e

        return self.datastore[key]

    def __setitem__(self,key,value):
        self.datastore[key] = value

    def __delitem__(self, key):
        
        if key not in self.datastore:
            if not self.bypass_del_KeyError:
                raise KeyError(f"Key {key} was not found in the datastore.")
            else:
                warnings.warn(f"Attemping to delete nonexistent key {key} in the datastore. Ignoring del statement...")
        else:
            del self.datastore[key]

    def is_defined(self,key):
        return key in self.datastore

    def is_set(self,key): #PHP-style `isset` function
        return key in self.datastore and key is not None

    def __init__(self, default_factory =None, bypass_del_KeyError=False):

        self.datastore = {}

        self.raise_KeyError = False

        if default_factory is None:
            self.raise_KeyError = True

        self.bypass_del_KeyError = bypass_del_KeyError

该代码可正常运行,但添加以下__getattr__和__setattr__方法以实现SimpleNamespace式功能时,触发了无限递归错误:

def __getattr__(self,attr_name):
    return self.__getitem__(attr_name)

def __setattr__(self,attr_name,value):
    self.__setitem__(attr_name,value)

报错回溯信息:

File "G:\My Drive\Image Processing\Mapping Project\core\types.py", line 30, in __getattr__
    return self.__getitem__(attr_name)Lab
  File "G:\My Drive\Image Processing\Mapping Project\core\types.py", line 14, in __getitem__
    if self.raise_KeyError and key not in self.datastore:
  File "G:\My Drive\Image Processing\Mapping Project\core\types.py", line 30, in __getattr__
    return self.__getitem__(attr_name)
  File "G:\My Drive\Image Processing\Mapping Project\core\types.py", line 14, in __getitem__
    if self.raise_KeyError and key not in self.datastore:
  File "G:\My Drive\Image Processing\Mapping Project\core\types.py", line 30, in __getattr__
    return self.__getitem__(attr_name)
  File "G:\My Drive\Image Processing\Mapping Project\core\types.py", line 14, in __getitem__
    if self.raise_KeyError and key not in self.datastore:
  File "G:\My Drive\Image Processing\Mapping Project\core\types.py", line 30, in __getattr__
    return self.__getitem__(attr_name)
RecursionError: maximum recursion depth exceeded

问题原因

无限递归的核心原因是:

  • 自定义的__setattr__会拦截所有属性赋值操作,包括初始化时self.datastore = {}、self.raise_KeyError = False这些类自有属性的设置,导致这些属性没有被存入实例的__dict__,而是被当作键值对存到了self.datastore字典中。
  • 当__getitem__方法中访问self.raise_KeyError或self.datastore时,Python找不到这些实例属性,会触发__getattr__,进而调用__getitem__,形成循环调用。

解决方案

修改__setattr__和__getattr__,区分类的自有属性和用户要存储的键值对:

  • 在__setattr__中,若要设置的是类的自有属性(datastore、raise_KeyError、bypass_del_KeyError、default_factory),则调用父类的__setattr__直接设置到实例;否则调用__setitem__存入datastore。
  • __getattr__保持逻辑不变,但此时自有属性已正确存入实例,不会触发不必要的调用。

修改后的完整代码:

import warnings

class fluiddict:
    """!    A class that emulates a dictionary, while also being able to support attribute assignment and default values.
            The default value of `default_factory` is None. This means a KeyError will be raised when non-existent data is requested
            To specify a default value of None, use `default_factory=lambda key: None`
    """
    # 定义类的自有属性列表
    _own_attrs = {'datastore', 'raise_KeyError', 'bypass_del_KeyError', 'default_factory'}

    def __contains__(self, key):
        return key in self.datastore

    def __getitem__(self,key):
        if self.raise_KeyError and key not in self.datastore:
            raise KeyError(f"Key '{key}' was not found in the datastore and no default factory was provided.")

        if key not in self.datastore:
            try:
                return self.default_factory(key)
            except Exception as e:
                print("An unknown exception occured while trying to provide a default value. Is your default factory valid?")
                raise e

        return self.datastore[key]

    def __setitem__(self,key,value):
        self.datastore[key] = value

    def __delitem__(self, key):
        
        if key not in self.datastore:
            if not self.bypass_del_KeyError:
                raise KeyError(f"Key {key} was not found in the datastore.")
            else:
                warnings.warn(f"Attemping to delete nonexistent key {key} in the datastore. Ignoring del statement...")
        else:
            del self.datastore[key]

    def is_defined(self,key):
        return key in self.datastore

    def is_set(self,key): #PHP-style `isset` function
        return key in self.datastore and key is not None

    def __init__(self, default_factory =None, bypass_del_KeyError=False):
        # 直接用父类__setattr__设置自有属性,避免被自定义__setattr__拦截
        super().__setattr__('datastore', {})
        super().__setattr__('raise_KeyError', False)
        super().__setattr__('default_factory', default_factory)
        if default_factory is None:
            super().__setattr__('raise_KeyError', True)
        super().__setattr__('bypass_del_KeyError', bypass_del_KeyError)

    def __getattr__(self, attr_name):
        return self.__getitem__(attr_name)

    def __setattr__(self, attr_name, value):
        if attr_name in self._own_attrs:
            super().__setattr__(attr_name, value)
        else:
            self.__setitem__(attr_name, value)

验证

现在可以正常使用属性访问和字典访问两种方式:

fd = fluiddict(default_factory=lambda k: 0)
fd.name = "test"
print(fd.name)  # 输出 test
print(fd['age']) # 输出 0
print(fd.datastore) # 输出 {'name': 'test'}

内容的提问来源于stack exchange,提问作者Michael Sohnen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 14:05:46