求itertools.product真实实现:自定义版本内存占用过高问题
itertools.product的真实实现为啥不占内存?
先说说官方文档示例为啥坑人
你从官方文档复制的示例代码,说白了就是先把所有笛卡尔积结果全生成出来存成列表,再挨个yield。比如常见的示例大概是这样:
def product(*args, repeat=1): pools = [tuple(pool) for pool in args] * repeat result = [[]] for pool in pools: result = [x+[y] for x in result for y in pool] for prod in result: yield tuple(prod)
这种写法里,result会越变越大,比如repeat=30的话,直接就内存爆炸了——毕竟2^30个元素根本存不下。
官方itertools.product的真实套路
标准库的itertools.product是用C写的,核心逻辑是靠追踪状态+懒加载,每次只生成当前需要的元素,根本不存全量结果。用Python模拟的话,大概是这个意思:
def efficient_product(*args, repeat=1): pools = [tuple(pool) for pool in args] * repeat # 用数组记录每个输入序列当前用到的索引,初始全是0 indices = [0] * len(pools) # 先返回第一个元素 yield tuple(pool[i] for pool, i in zip(pools, indices)) while True: # 从后往前找第一个能递增的索引(类似数字进位) for i in reversed(range(len(pools))): indices[i] += 1 if indices[i] < len(pools[i]): # 后面的索引重置为0 for j in range(i+1, len(pools)): indices[j] = 0 yield tuple(pool[i] for pool, i in zip(pools, indices)) break else: # 所有索引都没法递增了,结束 return
这个写法的关键:
- 懒加载到底:每次只生成当前要返回的元素,全程没搞大列表存结果
- 状态追踪:用
indices数组记着每个输入序列的当前位置,就像数数字一样,最后一位满了就进位,前面加1后面归0 - 内存占用极低:不管你repeat多少,内存只占个索引数组的空间,跟结果总数没关系
针对你的需求:固定最后几个元素的优化
如果要固定最后几个元素,只需要改改索引的更新逻辑——让最后几个索引不动,只更新前面的部分就行。比如固定最后2个元素的版本:
def product_with_fixed_suffix(*args, fixed_suffix, repeat=1): args_list = list(args) * repeat # 先检查固定后缀长度合不合理 assert len(fixed_suffix) <= len(args_list), "固定后缀长度不能超过总序列数" # 拆分可变部分和固定部分 variable_pools = args_list[:-len(fixed_suffix)] fixed_pools = args_list[-len(fixed_suffix):] # 确认固定元素都在对应序列里 for pool, val in zip(fixed_pools, fixed_suffix): assert val in pool, f"元素{val}不在对应的序列{pool}里" # 如果没有可变部分,直接返回固定后缀 if not variable_pools: yield tuple(fixed_suffix) return indices = [0] * len(variable_pools) # 生成第一个结果 yield tuple(pool[i] for pool, i in zip(variable_pools, indices)) + tuple(fixed_suffix) while True: # 只更新可变部分的索引 for i in reversed(range(len(variable_pools))): indices[i] += 1 if indices[i] < len(variable_pools[i]): for j in range(i+1, len(variable_pools)): indices[j] = 0 yield tuple(pool[i] for pool, i in zip(variable_pools, indices)) + tuple(fixed_suffix) break else: return
比如调用product_with_fixed_suffix(range(2), range(2), fixed_suffix=(1,), repeat=2),只会生成前面3个可变位置的组合,加上固定的最后一位1,根本不会生成全量的8个元素,内存占用特别小。
内容的提问来源于stack exchange,提问作者Filip Čermák
相关产品推荐
相关产品推荐

