
本文介绍一种不依赖递归的迭代算法,用于安全、高效地扁平化任意深度的嵌套字典/列表结构,彻底规避 python 默认递归限制与栈溢出风险,并支持超深嵌套(数百层)场景。
本文介绍一种不依赖递归的迭代算法,用于安全、高效地扁平化任意深度的嵌套字典/列表结构,彻底规避 python 默认递归限制与栈溢出风险,并支持超深嵌套(数百层)场景。
在实际数据处理中(如解析 API 响应、配置文件或 JSON Schema),我们常遇到动态、不可预知深度的嵌套结构——它可能包含多层嵌套的 dict 和 list,甚至混合出现。虽然递归实现直观简洁,但 Python 默认递归深度限制(通常为 1000)极易被突破;而盲目调高 sys.setrecursionlimit() 不仅治标不治本,还可能导致解释器崩溃或内存耗尽。更关键的是,真正的瓶颈往往不是“深度”,而是隐含的循环引用或指数级分支爆炸(例如每层含 2 个子项、100 层即产生 $2^{100}$ 个路径),此时任何算法都无法在有限资源内完成。
因此,高性能、生产就绪的扁平化方案必须满足三点:
✅ 无递归调用 —— 完全规避栈溢出;
✅ 惰性生成(generator) —— 避免一次性构建巨大列表,节省内存;
✅ 循环引用防御 —— 显式检测并跳过已访问对象,防止无限遍历。
以下是一个健壮、通用、可直接复用的迭代式扁平化实现:
def flatten(data):
"""
迭代式扁平化嵌套结构(dict/list),返回值生成器。
自动跳过循环引用,适用于任意深度(包括数百层)。
"""
from collections.abc import Mapping, Sequence
seen = set() # 用于检测循环引用(基于 id)
stack = [iter([data])]
while stack:
try:
item = next(stack[-1])
item_id = id(item)
# 检测循环引用:若对象已被访问过,跳过
if isinstance(item, (Mapping, Sequence)) and not isinstance(item, (str, bytes)):
if item_id in seen:
continue
seen.add(item_id)
# 若为 dict 或 list,压入其元素迭代器
if isinstance(item, Mapping):
stack.append(iter(item.values()))
elif isinstance(item, Sequence) and not isinstance(item, (str, bytes)):
stack.append(iter(item))
else:
yield item # 叶子节点:基本类型(str, int, float, bool, None 等)
except StopIteration:
stack.pop()
# 清理已出栈对象的引用记录(可选,提升内存友好性)
if stack:
# 注意:此处不主动清理 seen,因 id 复用概率低且清理逻辑复杂;
# 实际高负载场景可考虑 weakref.WeakSet 替代
pass✅ 使用示例
data = {
"name": "John",
"contacts": [
{"type": "email", "value": "[email protected]"},
{"type": "phone", "value": [{"country": "US", "number": "123-456-7890"}]}
],
"address": {"city": "New York", "coordinates": [{"lat": 40.7128, "lon": -74.0060}]}
}
# 获取扁平化结果(惰性生成,内存友好)
flattened = list(flatten(data))
print(flattened)
# 输出: ['John', 'email', '[email protected]', 'phone', 'US', '123-456-7890', 'New York', 40.7128, -74.0060]⚠️ 关键注意事项
-
字符串与字节串不展开:
isinstance(item, (str, bytes))被显式排除在容器判断之外,避免将"hello"错误拆解为['h','e','l','l','o']; -
循环引用防护:通过
id()快速标记已遍历对象,对含自引用的结构(如a = {}; a['self'] = a)安全鲁棒; -
内存效率优先:返回
generator,如需列表则显式调用list(flatten(data));处理 TB 级数据时,可配合for value in flatten(data): process(value)流式处理; -
类型兼容性:使用
collections.abc.Mapping/Sequence而非硬编码dict/list,天然支持OrderedDict、defaultdict、tuple、deque等标准容器。
? 是否需要第三方库?
对于绝大多数场景,上述纯 Python 实现已足够高效稳定。若项目已引入 toolz 或 more-itertools,可参考其 deepmap 或 collapse 工具,但它们同样基于迭代栈,且默认不内置循环引用检查。真正需要外部库的场景,通常是需结合 schema 验证、类型转换或异步 I/O —— 此时建议封装为独立模块,而非依赖通用扁平化工具。
总结:面对深度嵌套,放弃递归是第一步;采用显式栈 + 生成器 + 循环检测,才是兼顾安全性、性能与可维护性的工程实践。


















