Python 标准库中的 itertools 模块是处理迭代器的宝藏工具,其核心函数采用惰性求值机制,不一次性加载全部数据,处理超大文件时内存占用极低,且底层用 C 实现,速度远超手写循环。理解 itertools 之前需要区分可迭代对象(Iterable)和迭代器(Iterator):前者能被 for 循环遍历,后者只能调用 next() 向前获取元素,所有 itertools 函数返回的都是迭代器。
一、最常用的函数:日常代码的主力军
chain() 与 chain.from_iterable()
chain() 将多个可迭代对象首尾拼接成一个迭代器,不生成新列表。例如:- from itertools import chain
- result = list(chain([1, 2], [3, 4], [5, 6]))
- # 输出 [1, 2, 3, 4, 5, 6]
复制代码 chain.from_iterable() 用于展平嵌套列表,避免手动解包:- nested = [[1, 2], [3, 4], [5, 6]]
- result = list(chain.from_iterable(nested))
- # 输出 [1, 2, 3, 4, 5, 6]
复制代码 实际场景:合并爬虫采集的多个页面数据,比 sum(lists, []) 更高效。
islice() — 迭代器切片
对迭代器执行类似列表的切片操作,但不支持负索引。- from itertools import count, islice
- # 从无限计数器中取前5个
- first_five = list(islice(count(10), 5))
- # 输出 [10, 11, 12, 13, 14]
- # 读取大文件的第4到10行,无需载入全部
- with open("bigfile.txt") as f:
- lines = list(islice(f, 3, 10))
复制代码
groupby() — 分组(注意预排序)
按照键将连续相同元素分组,类似 Unix 的 uniq 命令。重要:必须先按分组键排序,否则结果异常。- from itertools import groupby
- data = [
- {"name": "Alice", "dept": "Engineering"},
- {"name": "Bob", "dept": "Engineering"},
- {"name": "Carol", "dept": "Marketing"},
- ]
- data.sort(key=lambda x: x["dept"])
- for dept, members in groupby(data, key=lambda x: x["dept"]):
- print(dept, list(members))
复制代码
accumulate() — 累积计算
默认做累加,但可接受任意二元函数,实现累积乘积、滚动最大值等。- from itertools import accumulate
- import operator
- nums = [1, 2, 3, 4, 5]
- # 累加:1 3 6 10 15
- print(list(accumulate(nums)))
- # 累积乘积:1 2 6 24 120
- print(list(accumulate(nums, operator.mul)))
- # 带初始值
- print(list(accumulate(nums, initial=100)))
- # 输出 [100, 101, 103, 106, 110, 115]
复制代码
pairwise() 与 batched() — 相邻配对与批量分组
pairwise(Python 3.10+)生成相邻元素配对,便于计算差值:- from itertools import pairwise
- prices = [100, 105, 98, 110]
- changes = [b - a for a, b in pairwise(prices)]
- # 输出 [5, -7, 12]
复制代码 batched(Python 3.12+)将迭代器按固定大小分批,替代手写切片循环:- from itertools import batched
- data = range(10)
- for batch in batched(data, 3):
- print(batch)
- # 输出 (0,1,2) (3,4,5) (6,7,8) (9,)
复制代码
二、进阶函数:用得少但关键时刻救命
starmap() — 解包参数映射
当函数需要接收元组参数时,比 map() 更简洁:- from itertools import starmap
- pairs = [(2, 5), (3, 2), (10, 3)]
- result = list(starmap(pow, pairs))
- # 输出 [32, 9, 1000]
复制代码
tee() — 复制迭代器(注意内存)
将迭代器复制为 n 个独立副本,但内部会缓存进度差,造成内存消耗。使用后原迭代器不应再使用。- from itertools import tee
- a, b = tee([1, 2, 3, 4, 5])
- print(list(a)) # [1, 2, 3, 4, 5]
- print(list(b)) # [1, 2, 3, 4, 5]
复制代码
zip_longest() — 补齐的 zip
以最长序列为准,短序列用 fillvalue 填充,避免丢失数据:- from itertools import zip_longest
- a = [1, 2, 3, 4]
- b = ['x', 'y']
- result = list(zip_longest(a, b, fillvalue='-'))
- # 输出 [(1,'x'), (2,'y'), (3,'-'), (4,'-')]
复制代码
takewhile() 与 dropwhile() — 条件截断
takewhile 从开头取满足条件的元素,遇到第一个不满足即停止;dropwhile 跳过开头满足条件的元素,然后输出剩余全部(不再判断)。- from itertools import takewhile, dropwhile
- data = [1, 4, 6, 3, 8]
- list(takewhile(lambda x: x < 5, data)) # [1, 4]
- list(dropwhile(lambda x: x < 5, data)) # [6, 3, 8]
复制代码
三、无限迭代器:谨慎使用,必须配合截断
count(start, step) 无限计数;cycle 无限循环序列;repeat 重复对象。使用时需配合 islice、takewhile 或 zip 截断,否则死循环。- from itertools import count, cycle, repeat
- # count 配合 zip 实现自定义起始行号
- for i, item in zip(count(1001), ["apple", "banana"]):
- print(i, item)
- # 输出 (1001, 'apple') (1002, 'banana')
- # repeat 重复固定次数
- list(repeat(7, 3)) # [7, 7, 7]
复制代码 注意:cycle 会复制整个序列到内存,处理大序列时需谨慎。
四、组合迭代器:排列组合的数学工具
combinations(组合,顺序无关不重复)、permutations(排列,顺序有关不重复)、product(笛卡尔积)、combinations_with_replacement(允许重复的组合)。- from itertools import combinations, permutations, product, combinations_with_replacement
- items = ['A', 'B', 'C']
- # 组合:C(3,2)=3
- list(combinations(items, 2)) # [('A','B'),('A','C'),('B','C')]
- # 排列:P(3,2)=6
- list(permutations(items, 2)) # [('A','B'),('A','C'),('B','A'),('B','C'),('C','A'),('C','B')]
- # 笛卡尔积
- list(product([0,1], repeat=3)) # 8个二进制
- # 有放回组合
- list(combinations_with_replacement('AB', 2)) # [('A','A'),('A','B'),('B','B')]
复制代码
五、实战:组合使用才是威力
单个函数是积木,组合可搭建高效数据处理流水线。
场景:分页 API 跳过前两页取后三页并展平- from itertools import chain, islice
- def fetch_page(n):
- return [n * 10 + i for i in range(5)]
- pages = (fetch_page(n) for n in range(10))
- result = list(chain.from_iterable(islice(pages, 2, 5)))
- # 输出 [20,21,22,23,24, 30,31,32,33,34, 40,41,42,43,44]
复制代码 场景:找出列表中连续递增的片段- from itertools import groupby, pairwise
- nums = [1,2,3,1,2,4,5]
- diffs = [b - a for a, b in pairwise(nums)]
- # 将连续 +1 的差值分组
- result = []
- for k, g in groupby(enumerate(diffs), lambda x: x[1]):
- if k == 1:
- indices = [i for i,_ in g]
- result.append((indices[0], indices[-1]+2))
- # 结果表示从第0到第2、第4到第6为连续递增片段
复制代码
六、注意事项
1. 迭代器只能遍历一次,需要多次使用请转为列表或使用 tee()。
2. groupby 必须预排序,否则分组异常。
3. 无限迭代器必须配合截断函数(islice、takewhile 等)使用,避免死循环。
4. cycle 内部会复制整个序列,内存敏感时需注意。
5. islice 不支持负索引,取最后 N 个元素可用 collections.deque(iterable, maxlen=N)。
掌握 itertools 的关键不是背参数,而是写代码时自然想到用 accumulate、chain.from_iterable 等替代手写循环,写出既优雅又高效的 Python 代码。 |