应优先使用 itertools.permutations 生成所有长度为 r 的不重复元素排列,因其底层 C 实现比手写递归快 5–10 倍、内存可控且返回迭代器;注意它不自动值去重,含重复元素时需额外处理。

什么时候该用 itertools.permutations 而不是手写递归
当你需要生成所有长度为 r 的不重复元素排列,且元素本身可哈希(比如数字、字符串、元组),itertools.permutations 是首选。它底层用 C 实现,比纯 Python 递归快 5–10 倍,内存也更可控——它返回迭代器,不一次性构建全部结果。
常见错误是传入含重复值的列表却期望去重排列:比如 permutations(['a', 'a', 'b'], 2) 会输出 ('a', 'a')、('a', 'b')、('a', 'a')、('a', 'b')、('b', 'a')、('b', 'a') —— 注意 ('a', 'b') 出现两次,因为两个 'a' 被视为不同位置的元素。如果真要“值去重”的排列,得套一层 set 或用 more-itertools.distinct_permutations。
- 参数
r可省略,默认为len(iterable);设为None不会报错,但结果和不传一样 - 输入必须是可迭代对象,但内部元素类型不限(支持嵌套结构,如
permutations([(1,2), (3,4)], 2)) - 别对超长序列(比如
len=12)直接转list,容易 OOM;用itertools.islice控制取前 N 个更安全
itertools.combinations 和 combinations_with_replacement 的关键区别
combinations(iterable, r) 生成无序、无放回的子集,比如 combinations('ABC', 2) → ('A','B'), ('A','C'), ('B','C');而 combinations_with_replacement 允许同一元素重复出现,比如 combinations_with_replacement('AB', 2) → ('A','A'), ('A','B'), ('B','B')。
容易混淆的是:两者都不关心原始顺序,但都严格按输入顺序输出结果。例如 combinations([3,1,2], 2) 输出的是 (3,1), (3,2), (1,2),不是按数值排序后的组合。如果业务要求字典序或数值序,得先对输入 sorted() 再传入。
立即学习“Python免费学习笔记(深入)”;
-
combinations要求r ≤ len(iterable),否则返回空迭代器;combinations_with_replacement没这限制 - 三者(
permutations/combinations/combinations_with_replacement)都要求r ≥ 0,负数会直接抛ValueError - 性能上,
combinations_with_replacement比combinations略慢,因需额外处理重复索引逻辑
用 itertools.product 替代多层 for 循环的实战场景
当你要做笛卡尔积(比如枚举所有参数组合、生成测试用例、构造路径),product(a, b, c) 比 for x in a: for y in b: for z in c: 更简洁、更省内存、且支持 repeat 参数。例如 product([0,1], repeat=3) 直接生成所有 3 位二进制元组:(0,0,0), (0,0,1), ..., (1,1,1)。
注意:默认行为是“有放回”乘积,即每个位置独立从对应 iterable 中取值。如果某位置要“无放回”(比如从同一列表中选不同元素),不能直接用 product,得换 permutations 或手动过滤。
-
product(*lists, repeat=n)等价于product(*([lists] * n)),但后者更易读 - 传入空列表时,
product返回空迭代器;传入含空列表的元组,如product([1,2], []),也返回空 - 如果某个 iterable 是无限的(比如
count()),务必用islice截断,否则循环卡死
组合类函数的性能陷阱与调试技巧
最常被忽略的一点:所有 itertools 组合函数都基于索引运算,不检查元素是否相等。这意味着如果你传入一个包含重复元素的 list,它们会照常生成大量“逻辑重复”的结果——这不是 bug,是设计使然。调试时若发现结果数量远超预期,第一反应应是检查输入是否有意料外的重复值,而不是怀疑函数用错了。
- 用
len(list(itertools.xxx(...)))测数量前,先估算理论值:排列是P(n,r) = n!/(n−r)!,组合是C(n,r) = n!/(r!(n−r)!),笛卡尔积是∏|i| - 在 Jupyter 或 REPL 中调试时,别直接打印整个迭代器,用
list(islice(xxx, 5))看前 5 个就够了 - 想把结果转成 set 去重?小心:元组可哈希,但含 list 或 dict 的元组不行,会抛
TypeError
真正麻烦的从来不是调用函数,而是搞清你要的到底是“位置组合”还是“值组合”,以及是否允许重复选取——这决定了该用哪个函数、要不要预处理输入、以及后续怎么过滤。


















