如何将递归式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
关键修正点说明
- 栈元素扩展:新增父容器、父容器中的键/索引,确保每个节点处理完成后能将结果写入父级对应位置。
- 处理顺序调整:对字典和可迭代对象的元素采用反向入栈,保证处理顺序与递归版本一致(递归从左到右,栈后进先出,反向入栈后弹出顺序为正序)。
- _ast属性处理:传递
classkey参数,确保后续逻辑与递归一致。 - 自定义类实例处理:先创建空字典作为容器,处理完所有属性后再添加
classkey对应的类名。 - 根节点初始化:根据输入对象类型提前创建根容器,避免结果混乱。
内容的提问来源于stack exchange,提问作者Duong Duc
相关产品推荐
相关产品推荐

