Python itertools 模块

itertools 算是平时比较容易忽略的模块吧, 至少我在平时比较少用到它. 只记得里面有个 groupby . 但是作为标准包的一部分, 还是有一些值得一试的功能的. itertools 里面提供的工具, 有一个特点, 他们都是惰性求值(lazy evaluation), 惰性求值相对立的是及早求值(eager evaluation). 更能体现惰性求值的名词是 call_by_need, 当你需要时才求值.

在大部分情况下, 及早求值的行为是与我们预期的行为相同的. 惰性求值也可以与及早求值有同样的行为, 达到在不增加代码复杂度的情况下, 降低空间复杂度, 也可以然计算过程更平滑, 平衡CPU的负载. 但是在某些场景下, 我们需要做一些预处理, 也有一些场景可能无法做到惰性求值. 比如 itertools 提供的 groupby 工具, 如果输入数据无序, 我们很有可能无法获得与及早求值相同的分组结果. 再说到对序列排序这个操作, 它是无法惰性求值的.

概览

无限序列
  • count 计数君
  • cycle 循环器
  • repeat 重复是人类的本质
输入有限时, 有限的序列
  • accumulate 累加/减/乘...
  • chain 连接多个序列
  • chain.from_iterable  连接多个序列
  • compress 选择性的输出
  • dropwhile
  • groupby 分组
  • filterfalse
  • islice 惰性切片
  • starmap map + * 解包
  • tee
  • takewhile
  • zip_longest
排列组合系列
  • product
  • permutations
  • combinations
  • combinations_with_replacement

count

count(start=0, step=1) , start 指定计数的起点, step 指定每次跳跃的长度. start, step 的取值可以是全体实数, 所以他是可以接受小数的. 而 step 的正负决定了这个迭代器产生的元素是上升序列还是下降序列.  count 与 range 比较像, 一个区别是 range 接受 stop 参数, 但是 count 不接受, 也就意味着用 count 生成的迭代器是一个无限的迭代器. 另一个区别是 range 所有参数都只接受 int 类型.

from itertools import count

rise_seq = count(0, 2) # OK
down_seq = count(0.1, -0.9) # OK

# 在这里我做了一些小尝试
# float('inf') 是 Python 里面表示无穷大的一个数
# 我本来想尝试 用 range 模拟一下 count 的功能
# 但是异常了, 我才知道原来 range 只能接受整数参数
# 现学现卖系列
range(0, float('inf'), 1)

cycle

cycle(p), 参数p 是一个序列, cycle 会重复的依次输出序列中每一个元素. p0, p1, p2 ... plast, p0, p1 ..., cycle, 它和count一样也是一个无限的迭代器.

from itertools import cycle


c = cycle([1,2,3]) # 1, 2, 3, 1, 2, 3 ...

repeat

repeat(elem [,n]) 会重复的生成参数 elem n 次, 因为 n 是可选参数, 所以当没有设置 n 时, 它也是一个无限的迭代器.

from itertools import repeat


r = repeat(1) # 1, 1, 1 ...
r = repeat(1, 3) # OK 1, 1, 1

accumulate

accumulate(p[, func]) 默认生成一个累加序列, p0, (p0 + p1), (p0 + p1 + p2) ... (p0 + ... + pn), 通过指定 func 参数, 它也可以生成累乘, 累除, 累减等序列.

from itertools import accumulate
import operator


# 累加
print(list(accumulate([1, 2, 3])))
# [1, 3    , 6        ]
# [1, 1 + 2, 1 + 2 + 3]
# 累减
print(list(accumulate([1, 2, 3], func=operator.sub)))
# [1, -1   , -4       ]
# [1, 1 - 1, 1 - 2 - 3]
# operator 还包括很多操作可以当作 accumulate 的参数.

chain, chain.from_iterable

chain(p, q, ...),  chain.from_iterable([p, q, ...])类似于 flatmap 的概念吧, 将序列扁平化. 生成 p0, p1 ... plast, q0, q1 ... qlast ...

# 比如考虑一个需求,有一个字符串的列表['hello', 'world']
# 但是我们希望 'h', 'e', 'l', 'l', 'o', 'w' 'o', 'r', 'l', 'd'

from itertools import chain

strings = ['hello', 'world']
for c in chain(*strings):
    print(c)
    
for c in chain.from_iterable(strings):
    print(c)

compress

compress(data, selectors) 不太好描述, 选择器? 它会生成根据 selectors, 生成序列. (data[0] if s[0]), (data[1] if s[1]), .....

from itertools import compress


data = range(10)
# n & 1 奇数: 1, 偶数: 0
selectors = [n & 1 for n in range(10)]
# [0, 1, 0, 1 ...]

for i in compress(data, selectors):
    print(i)
# 1, 3, 5, 7, 9

dropwhile

dropwhile(pred, seq) pred 是一个可调用对象, 接收一个参数. dropwhile 会丢弃掉 第一个不满足 pred(n) 之前的所有元素, 输出包括这个元素在内, 之后所有的元素.

for i in dropwhile(lambda x: x < 5, range(10)):
	print(i)
    
# 5, 6, 7, 8, 9

groupby

groupby(iterable[, keyfunc]) 接收一个可迭代对象, 以及一个可选的 keyfunc 指定怎么样分组. 分组的依据是 n1 == n2, 当制定了 keyfunc时, 就是 keyfunc(n1) == keyfunc(n2)

from itertools import groupby

s = 'helloworld'
res = {}
for c, values in groupby(s):
    t = []
    for value in values:
        t.append(value)
    res[c] = t
    
print(res)
# {'h': ['h'], 'e': ['e'], 'l': ['l'], 'o': ['o'], 
#  'w': ['w'], 'r': ['r'], 'd': ['d']}
# 这个输出与我们预想的不符合, hello world 中有两个o, 三个l
# 但是分组的结果里面, 却都只有一个
for c, values in groupby(sorted(s)):
    t = []
    for value in values:
        t.append(value)
    res[c] = t
    
print(res)
# {'h': ['h'], 'e': ['e'], 'l': ['l', 'l', 'l'], 
#  'o': ['o', 'o'], 'w': ['w'], 'r': ['r'], 'd': ['d']}  

filterfalse

filterfalse(pred, seq) pred 是一个可调用对象, filterfalse 会将seq中每一个元素都应用一次 pred, 输出结果为 False 的元素.

islice

islice(seq, [ start,] stop [, step]) 是一个切片操作, 输出 seq[start:stop:step]

starmap

starmap(fun, seq) starmap 是 map 的变种, star 的意思是在应用 seq 中每个元素调用 fun 时, 会用星号对元素进行解包. 也就是 fun(*seq[0])

tee

tee(it, n=2) 将一个序列, 复制成 n 份迭代器, 并且打包成元组返回

from itertools import tee

origin = [1, 2, 3, 4]
double = tee(origin, 2)
print(list(double[0]))
# [1, 2, 3, 4]
print(list(double[1]))
# [1, 2, 3, 4]
展示评论