该用 itertools.chain 而非 + 或 chain.from_iterable 时:当需懒连接多个已知的、一次性/内存敏感的迭代器(如文件句柄、生成器);chain(a, b, c) 适用于明确变量名的独立迭代器,chain.from_iterable([a,b,c]) 适用于它们被封装在单个容器中,而 + 会报错或耗尽资源。

什么时候该用 itertools.chain 而不是 + 或 itertools.chain.from_iterable
直接拼接多个 list 时用 + 没问题,但一旦涉及生成器、文件句柄、数据库游标这类**一次性、不可重用、内存敏感**的迭代器,+ 就会报错或耗尽资源。itertools.chain 是唯一能真正“懒连接”它们的方式——不提前展开、不复制元素、只在迭代时逐个拉取。
itertools.chain.from_iterable 适合输入本身是一个**可迭代对象的容器**(比如 [[1,2], [3,4], range(5)]),而 itertools.chain(a, b, c) 更直白:你明确知道要连哪几个独立迭代器。
- 用
chain(a, b, c):当你手头已有多个变量名明确的迭代器 - 用
chain.from_iterable([a, b, c]):当它们在一个列表/元组里,且你不想解包 - 别用
+:哪怕只是两个range对象,range(3) + range(5)会直接抛TypeError
itertools.chain 的参数陷阱:为什么传入嵌套结构会静默出错
常见错误是把一个列表当作单个参数传进去:chain([[1,2], [3,4]]),结果只遍历了外层列表的两个子列表对象,而不是它们的元素。它等价于 for x in [[1,2], [3,4]]: yield x,不是你想要的扁平化效果。
正确做法只有两种:
立即学习“Python免费学习笔记(深入)”;
- 显式解包:
chain(*[[1,2], [3,4]]) - 改用
chain.from_iterable([[1,2], [3,4]])
注意:如果误写成 chain([1,2], [3,4])(没加星号),反而没问题——因为这是两个独立参数,chain 本就接受变长参数。
和 yield from 对比:什么情况下必须选 chain
yield from 在生成器函数里也能串联迭代器,但它要求你写函数;chain 是即用即走的工具函数,不侵入逻辑结构。更重要的是:yield from 无法处理“动态数量”的迭代器拼接,而 chain 可以配合 *args 或 from_iterable 灵活应对。
- 固定几个已知迭代器 →
yield from a; yield from b可读性略好 - 来自配置、API 响应或用户输入的不确定数量 → 必须用
chain(*iterators)或chain.from_iterable(iterators) - 需要嵌入表达式或 lambda 中 →
chain可直接构造,yield from不行
例如过滤后拼接多个文件行:chain.from_iterable(filter(bool, [open(f) for f in files])) —— 这种链式组合,yield from 很难干净实现。
性能提醒:chain 本身开销极小,但别让它掩盖真正的瓶颈
itertools.chain 几乎没有运行时开销,它只是维护一个当前迭代器索引和调用 next() 的薄层。但如果你用它连接 1000 个每次只返回 1 个元素的迭代器(比如大量小文件),Python 的迭代器切换成本会累积明显——这时不如先合并成大块再 chain。
- 避免:
chain(*[iter([x]) for x in huge_list])(每个元素单独包装成迭代器) - 推荐:
chain.from_iterable(chunked(huge_list, 1000))(批量分块) - 检查是否真需要惰性:如果最终要转成
list,且所有源都很小,直接sum(lists, [])可能更直观(尽管不推荐)
真正容易被忽略的是:chain 不改变底层迭代器行为。如果某个被链入的迭代器本身有副作用(如读文件、发 HTTP 请求),这些副作用会在你第一次遍历到它时才触发——不是 chain 调用时,而是 next() 到它的时候。


















