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

如何将递归式Python对象图转字典函数改写为非递归实现?

问题:递归Python对象转字典函数触发栈溢出,求非递归实现修正

我找到一个递归函数可以将Python对象图转为字典:

def todict(obj, classkey=None):
    if isinstance(obj, dict):
        data = {}
        for (k, v) in obj.items():
            data[k] = todict(v, classkey)
        return data
    elif hasattr(obj, "_ast"):
        return todict(obj._ast())
    elif hasattr(obj, "__iter__") and not isinstance(obj, str):
        return [todict(v, classkey) for v in obj]
    elif hasattr(obj, "__dict__"):
        data = dict([(key, todict(value, classkey)) 
            for key, value in obj.__dict__.items() 
            if not callable(value) and not key.startswith('_')])
        if classkey is not None and hasattr(obj, "__class__"):
            data[classkey] = obj.__class__.__name__
        return data
    else:
        return obj

但这个函数在我的项目里触发了最大递归深度限制,于是我尝试用栈改写为非递归实现,却无法得到和递归函数一致的结果,请求帮忙修正:

def to_dict(obj, classkey=None):
    stack = [(obj, classkey)]
    result = {}

    while stack:
        current_obj, key = stack.pop()

        # Check if current_obj is a dictionary
        if isinstance(current_obj, dict):
            data = {}
            for k, v in current_obj.items():
                stack.append((v, k))

        # Check if current_obj has _ast attribute
        elif hasattr(current_obj, "_ast"):
            stack.append((current_obj._ast()))

        # Check if current_obj is iterable (excluding strings)
        elif hasattr(current_obj, "__iter__") and not isinstance(current_obj, str):
            arr = [v for v in current_obj]
            for v in arr:
                stack.append((v, key))

        # Check if current_obj has __dict__ attribute
        elif hasattr(current_obj, "__dict__"):
            data = {}
            for k, v in current_obj.__dict__.items():
                if not callable(v) and not k.startswith("_"):
                    stack.append((v, k))
            if key is not None and hasattr(current_obj, "__class__"):
                data[key] = current_obj.__class__.__name__

        # Otherwise, it's a leaf node
        else:
            data = current_obj

        # If there's a key, update result accordingly
        if key is not None:
            if isinstance(result, list):
                result[key] = data
            elif isinstance(result, dict):
                result[key] = data
        else:
            result = data

    return result

修正后的非递归实现

原代码核心问题是未记录每个节点的父容器及对应位置,导致无法正确回溯赋值;同时处理_ast时未传递classkey,列表处理未记录索引,结果更新逻辑混乱。以下是修正后的版本:

def to_dict(obj, classkey=None):
    # 栈元素格式:(当前对象, 父容器, 父容器中的键/索引, classkey)
    stack = []
    root = None

    # 初始化根节点容器
    if isinstance(obj, dict):
        root = {}
        stack.append((obj, root, None, classkey))
    elif hasattr(obj, "_ast"):
        stack.append((obj._ast(), None, None, classkey))
    elif hasattr(obj, "__iter__") and not isinstance(obj, str):
        root = []
        stack.append((obj, root, None, classkey))
    elif hasattr(obj, "__dict__"):
        root = {}
        stack.append((obj, root, None, classkey))
    else:
        return obj

    while stack:
        current_obj, parent, parent_key, ck = stack.pop()

        # 处理字典类型
        if isinstance(current_obj, dict):
            data = {}
            if parent is not None:
                if isinstance(parent, dict):
                    parent[parent_key] = data
                elif isinstance(parent, list):
                    parent[parent_key] = data
            # 反向入栈保证处理顺序与递归一致
            for k, v in reversed(current_obj.items()):
                stack.append((v, data, k, ck))
        
        # 处理带_ast属性的对象
        elif hasattr(current_obj, "_ast"):
            stack.append((current_obj._ast(), parent, parent_key, ck))
        
        # 处理可迭代对象(排除字符串)
        elif hasattr(current_obj, "__iter__") and not isinstance(current_obj, str):
            data = []
            if parent is not None:
                if isinstance(parent, dict):
                    parent[parent_key] = data
                elif isinstance(parent, list):
                    parent[parent_key] = data
            # 反向入栈保证索引顺序正确
            for idx, v in reversed(list(enumerate(current_obj))):
                stack.append((v, data, idx, ck))
        
        # 处理自定义类实例(带__dict__属性)
        elif hasattr(current_obj, "__dict__"):
            data = {}
            if parent is not None:
                if isinstance(parent, dict):
                    parent[parent_key] = data
                elif isinstance(parent, list):
                    parent[parent_key] = data
            # 过滤属性后反向入栈
            items = [(k, v) for k, v in current_obj.__dict__.items() if not callable(v) and not k.startswith('_')]
            for k, v in reversed(items):
                stack.append((v, data, k, ck))
            # 添加classkey标识类名(如果需要)
            if ck is not None and hasattr(current_obj, "__class__"):
                data[ck] = current_obj.__class__.__name__
        
        # 处理叶子节点
        else:
            if parent is not None:
                if isinstance(parent, dict):
                    parent[parent_key] = current_obj
                elif isinstance(parent, list):
                    parent[parent_key] = current_obj
            else:
                return current_obj

    return root

关键修正点说明

  1. 栈元素扩展:新增父容器、父容器中的键/索引,确保每个节点处理完成后能将结果写入父级对应位置。
  2. 处理顺序调整:对字典和可迭代对象的元素采用反向入栈,保证处理顺序与递归版本一致(递归从左到右,栈后进先出,反向入栈后弹出顺序为正序)。
  3. _ast属性处理:传递classkey参数,确保后续逻辑与递归一致。
  4. 自定义类实例处理:先创建空字典作为容器,处理完所有属性后再添加classkey对应的类名。
  5. 根节点初始化:根据输入对象类型提前创建根容器,避免结果混乱。

内容的提问来源于stack exchange,提问作者Duong Duc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 12:44:54