因为itertools.combinations返回惰性迭代器,仅按需生成组合;而list()会强制展开全部c(n,k)个结果到内存,n≥30时易oom。应直接for遍历,用islice跳过前n项,避免全量转list。

为什么 itertools.combinations 不会爆内存,而 list(itertools.combinations(...)) 会?
因为 itertools.combinations 返回的是迭代器,每次只生成一个组合,不预先计算全部结果;一旦你用 list() 包裹它,Python 就会强制把所有组合一次性加载进内存,组合数是 C(n, k),n=20、k=10 时就有 184756 个元组,每个元组还带引用开销——小数据看不出问题,n≥30 就很容易 OOM。
实操建议:
- 永远优先用
for combo in itertools.combinations(iterable, r)直接遍历,别转list、tuple或deque - 如果后续要多次遍历,宁可重新调用
itertools.combinations,也不要缓存全部结果(除非你明确知道组合总数很小) - 注意:传入的
iterable本身要是可重复遍历的(比如list、tuple),不能是单次迭代器(如map()或文件行迭代器),否则第二次遍历时会空
当需要「跳过前 N 个组合」时,别用 list + 切片
常见错误写法:list(itertools.combinations(range(100), 5))[1000:] —— 这会先构造全部 C(100,5) ≈ 75M 个组合,再切片,内存和时间双爆炸。
正确做法是用 itertools.islice 流式跳过:
from itertools import combinations, islice
for combo in islice(combinations(range(100), 5), 1000, None):
process(combo)
关键点:
-
islice(iterator, start, stop)是惰性的,它内部只推进迭代器,不缓存跳过的项 -
start必须是非负整数;若需动态跳过(比如按条件),用itertools.dropwhile更合适 - 不要对
islice结果再套list,否则又回到内存陷阱
itertools.product 和 itertools.permutations 同样适用「流式处理」原则
这三个函数都返回迭代器,但用户常误以为 product「只是笛卡尔积所以安全」,其实它的空间复杂度是各维度长度乘积(比如 product(range(100), repeat=6) 有 10¹² 项),不流式处理照样崩。
使用场景提醒:
-
product(A, B, C):确保 A/B/C 是轻量可迭代对象(避免嵌套大列表);若某参数是生成器,记得它只能用一次 -
permutations(seq, r):当len(seq)较大时,r 接近 len(seq) 会导致阶乘级增长,比combinations更敏感 - 所有这些函数都不支持「按索引随机访问」——想取第 100 万个组合?得自己实现跳过逻辑或换用数学索引算法(如 combinatorial number system)
真正危险的不是 itertools,而是你把它当「容器」用
很多内存问题根源不在函数本身,而在调用链中无意触发了全量展开。比如:
- 传给
pandas.DataFrame()构造器 → 自动转 list - 用在字典推导式里:
{i: combo for i, combo in enumerate(combinations(...))}→ 强制展开全部 - 日志打印调试:
print(list(combinations(...)))在生产环境遗留未删
最容易被忽略的一点:itertools.chain(*list_of_iters) 中,如果 list_of_iters 是从 combinations 批量生成再存进列表的,那内存峰值就出现在「生成那个列表」时,而不是 chain 执行时。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











