Python 嵌套列表扁平化:五种写法与取舍

2026-05-11 21 预计阅读时间:1 分钟
来源:realpython.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:7 分钟

处理嵌套列表是日常数据清洗、日志解析、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 检查只覆盖了 listtuple,如果数据里混了 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,顺便享受向量化加速。

扁平化本身不难,难的是在可读性、内存、依赖、灵活性之间找到平衡。下次遇到嵌套列表,先问自己两个问题:嵌套深度规则吗?数据量多大? 答案一出,选型自然落地。


相关推荐