嵌套列表在 Python 开发中随处可见——CSV 读取、JSON 解析、API 返回的分组数据,都可能产生 [[1,2],[3,4],[5]] 这样的结构。当你需要把多层嵌套拍平成一维列表时,Python 提供了从朴素循环到库函数的多种路径。选哪一种,取决于数据规模、嵌套深度和你的可读性偏好。
朴素循环:最稳的基础写法
nested = [[1, 2, 3], [4, 5], [6]]
flat = []
for sublist in nested:
for item in sublist:
flat.append(item)
print(flat) # [1, 2, 3, 4, 5, 6]
两层 for 拼接,逻辑一目了然。对于小数据量或一次性脚本,这完全够用。缺点是三行代码才能完成一件事,且 append 在超长列表上不如预分配高效——不过大多数场景下这点开销可以忽略。
列表推导式:循环的紧凑版
nested = [[1, 2, 3], [4, 5], [6]]
flat = [item for sublist in nested for item in sublist]
print(flat) # [1, 2, 3, 4, 5, 6]
推导式把上面的双层循环压缩成一行。阅读顺序和循环一致:外层 for 在前,内层 for 在后。这是日常开发中最常见的写法——短、快、不需要额外导入。但嵌套超过两层时推导式会变得难读,该换方法了。
itertools.chain:处理大量子列表的利器
from itertools import chain
nested = [[1, 2, 3], [4, 5], [6]]
flat = list(chain.from_iterable(nested))
print(flat) # [1, 2, 3, 4, 5, 6]
chain.from_iterable 接收一个可迭代对象,逐个拉出子列表中的元素再串联。它不会先构建中间列表,内存占用更友好。当子列表数量成千上万时,这比推导式更省内存。
注意:chain(*nested) 也能工作,但 * 解包会把所有子列表同时展开为参数,子列表过多时反而失去内存优势。优先用 from_iterable。
functools.reduce:函数式风格的单行方案
from functools import reduce
nested = [[1, 2, 3], [4, 5], [6]]
flat = reduce(lambda x, y: x + y, nested)
print(flat) # [1, 2, 3, 4, 5, 6]
reduce 逐次把两个子列表拼接起来。写法简洁,但每次 + 都生成一个新列表,时间复杂度为 O(n²)。子列表少时无所谓,数据量大时性能会明显劣于 chain。如果偏爱函数式,可以改用 operator.add 替代 lambda,稍微提升可读性:
from functools import reduce
from operator import add
flat = reduce(add, nested)
NumPy:数值数据的专用通道
import numpy as np
nested = [[1, 2, 3], [4, 5], [6]]
arr = np.array(nested)
flat = arr.flatten()
print(flat) # array([1, 2, 3, 4, 5, 6])
如果你的数据本来就是数值型、且已经在用 NumPy 做计算,flatten() 或 ravel() 是最自然的选择。flatten() 返回副本,ravel() 尽量返回视图——修改视图会影响原数组,需留意。
不适合的场景:纯字符串列表、混合类型列表,或者项目里根本没有 NumPy。为了一次扁平化引入整个 NumPy,得不偿失。
递归:对付不规则深度嵌套
前面所有方法都假设嵌套深度固定为两层。遇到 [[1, [2, 3]], [4, [[5]]]] 这种不规则深度,递归才是正解:
def flatten_recursive(lst):
result = []
for item in lst:
if isinstance(item, list):
result.extend(flatten_recursive(item))
else:
result.append(item)
return result
nested = [[1, [2, 3]], [4, [[5]]]]
print(flatten_recursive(nested)) # [1, 2, 3, 4, 5]
递归逐层判断:是列表就继续深入,不是列表就收集。这能处理任意深度,但有两个风险——极深嵌套会触发 Python 的递归深度限制(默认 1000),且 isinstance(item, list) 只认 list,不认 tuple 等其他序列类型。生产环境中可改为 collections.abc.Iterable 判断,但要排除字符串(字符串也是 Iterable):
from collections.abc import Iterable
def flatten_deep(lst):
result = []
for item in lst:
if isinstance(item, Iterable) and not isinstance(item, (str, bytes)):
result.extend(flatten_deep(item))
else:
result.append(item)
return result
方法选择速查
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 两层嵌套,数据量小 | 列表推导式 | 一行搞定,无需导入 |
| 两层嵌套,子列表很多 | itertools.chain.from_iterable |
内存友好,不构建中间列表 |
| 已在用 NumPy 的数值数据 | arr.flatten() / arr.ravel() |
与数组生态一致 |
| 不规则深度嵌套 | 递归函数 | 其他方法无法处理 |
| 偏函数式、数据量小 | functools.reduce |
简洁但性能差,慎用于大数据 |
实际项目中最常用的组合是:推导式处理简单场景,chain 处理大量数据,递归处理深度嵌套。其余方法在特定条件下有价值,但不必刻意追求"一行搞定"而牺牲性能或可读性。