处理嵌套列表是日常数据清洗、日志解析、API 返回值拆包时的高频操作。一个 [[1,2],[3,4],[5]] 要变成 [1,2,3,4,5]——看似简单,但不同场景下写法差异很大,选错方法可能拖慢整条数据处理管线。
下面逐个拆解五种主流写法,每种都附带可直接运行的代码。
最直觉的 for 循环
先说最"笨"也最稳的方式——显式遍历、逐个追加:
nested = [[1, 2], [3, 4], [5]]
flat = []
for sublist in nested:
for item in sublist:
flat.append(item)
print(flat) # [1, 2, 3, 4, 5]
优点:逻辑一目了然,调试时可以随时插入 print 或条件判断;对任意可迭代对象都适用,不依赖子列表的具体类型。
缺点:两层循环写起来啰嗦,性能在百万级数据时不如后续方案。
什么时候用:需要在中途做过滤、转换或断点调试时,for 循环的可读性和可控性最强。
列表推导式:一行搞定
把两层循环压缩到一行,是 Python 社区最常见的"优雅写法":
nested = [[1, 2], [3, 4], [5]]
flat = [item for sublist in nested for item in sublist]
print(flat) # [1, 2, 3, 4, 5]
注意循环顺序:for sublist in nested 在外层,for item in sublist 在内层——和上面 for 循环的嵌套顺序完全一致,只是从"竖着写"变成了"横着写"。
取舍:短列表、纯扁平化、无额外逻辑时,推导式简洁高效。但如果要加 if 过滤或调用函数,一行会变得难以阅读,这时就该回到 for 循环。
itertools.chain:标准库的懒加载方案
itertools.chain.from_iterable 是处理大数据集的利器——它返回迭代器,不立即构建结果列表:
from itertools import chain
nested = [[1, 2], [3, 4], [5]]
flat_iter = chain.from_iterable(nested)
flat = list(flat_iter)
print(flat) # [1, 2, 3, 4, 5]
关键区别:chain.from_iterable 不会一次性把所有元素拷进内存。如果后续只需要逐个消费(比如写入文件、喂给另一个迭代器),可以不调用 list(),全程零额外内存开销。
什么时候用:数据量大、或只需要顺序消费而不需要随机访问时。比推导式更省内存,也比手写 for 循环更紧凑。
递归:对付不规则嵌套
前面三种方案都假设嵌套深度固定为两层。现实数据经常更混乱——[1, [2, [3, 4]], 5] 这种混合深度,推导式和 chain 都搞不定,递归才管用:
def flatten_recursive(data):
result = []
for element in data:
if isinstance(element, (list, tuple)):
result.extend(flatten_recursive(element))
else:
result.append(element)
return result
nested = [1, [2, [3, 4]], 5]
print(flatten_recursive(nested)) # [1, 2, 3, 4, 5]
风险提示:递归深度受 Python 默认限制(通常 1000 层)。遇到极端深嵌套时需要用 sys.setrecursionlimit 调高,或改用栈模拟的迭代版本。另外 isinstance 检查只覆盖了 list 和 tuple,如果数据里混了 set 或自定义容器,需要扩展判断条件。
NumPy:数值矩阵的批量操作
如果嵌套列表本质上是数值矩阵(行数固定、每行长度一致),NumPy 的 flatten() 或 ravel() 是性能最优的选择:
import numpy as np
nested = [[1, 2], [3, 4], [5, 6]] # 3×2 矩阵
arr = np.array(nested)
flat = arr.flatten()
print(flat) # [1 2 3 4 5 6] (ndarray)
print(flat.tolist()) # [1, 2, 3, 4, 5, 6] (原生 list)
flatten() 返回拷贝,ravel() 尽量返回视图(共享内存)。对大型数值数组,ravel() 更省内存,但修改视图会影响原数组——根据场景选择。
什么时候用:数据是规整数值矩阵、且后续还要做统计运算时。如果只是偶尔扁平化一个杂乱列表,引入 NumPy 的依赖成本不值得。
选型速查
把五种方案的关键差异压缩成一张表:
| 方案 | 嵌套深度 | 内存模式 | 依赖 | 适用场景 |
|---|---|---|---|---|
| for 循环 | 固定两层 | 立即构建 list | 无 | 需要中途过滤/调试 |
| 列表推导式 | 固定两层 | 立即构建 list | 无 | 纯扁平化、短数据 |
| itertools.chain | 固定两层 | 懒迭代 | 标准库 | 大数据、顺序消费 |
| 递归 | 不规则任意深度 | 立即构建 list | 无 | 混合深度嵌套 |
| NumPy flatten | 规整矩阵 | 拷贝/视图 | numpy | 数值矩阵、后续统计 |
实操建议:
- 日常脚本、数据量小:推导式,一行解决。
- 处理流式大数据:
chain.from_iterable,不提前分配内存。 - 日志/JSON 拆包,嵌套深度不确定:递归,但加深度保护。
- 科学计算、矩阵操作:NumPy,顺便享受向量化加速。
扁平化本身不难,难的是在可读性、内存、依赖、灵活性之间找到平衡。下次遇到嵌套列表,先问自己两个问题:嵌套深度规则吗?数据量多大? 答案一出,选型自然落地。