可迭代器(iterable),不仅限于list/str等,还包括任何包含有yield
关键字的函数,后者未必有规律的迭代特征。标准库中的itertools包提供了更加灵活的产生迭代器的工具,这些工具的输入大都是已有的迭代器函数的封装,并且itertools给出的函数都是针对广义迭代器而言。而len()
等函数是针对狭义迭代器,即sequence(i.e. str, list, tuple)而言的。
以内置函数range()
为例,执行结果会是一次性计算好整个序列。这对于很长的序列来说会比较耗时,甚至带来性能问题。因而,python还提供了内置函数,提供了惰性求值版本,那就是xrange()
。它利用yield
特性,第一次执行时仅仅返回迭代器,不到用时是不会求值的。
实际上,itertools提供的函数都是惰性的,并且给原内置函数都重写了惰性版本。如imap()
对于内置的map()
。
扩展库Pipe则对内置函数和部分itertools进行了封装,提供了类似unix bash下的管道式调用风格,更接近人类从左到右的阅读习惯,使得代码更加优雅。其他动态语言,如ruby, c#-lambda java8-lambda也都提供了类似的链式调用形式。
另外,也提供了@Pipe装饰器,可以非常方便地扩展出自己的管道函数,或者继续封装其他itertools中的有用函数。
这里是Pipe官方给出的例子,用管道函数式编程解出https://projecteuler.net/中的三道题目:
1 |
|
注意:所有惰性求值的迭代器,都是只能求值一次的,如果再次求值会什么也得不到,因为yield
堆栈已经走到底,无法回头。因此,当要对惰性迭代器重复使用时,必须故意地提前将其求值展开,或者利用itertools.tee
来克隆一个迭代器。
输入输出
1 | "42" | stdout # 输出到标准输出 >>> 42 |
数学运算
1 | [1, 2, 3, 4] | concat("#") # 连接字符串 >>>'1#2#3#4' |
组合工具
1 | (1, 2, 3, 4, 5, 6, 7, 8, 9) \ |
产生无限序列
1 | itertools.count(5, 2) # 从5开始的整数循环器,每次增加2,即5, 7, 9, 11, 13, 15 ...步长默认为1 |
序列运算
1 | xrange(5) | aggregate(lambda x, y: x + y, initializer=0) # 同内置reduce函数 >>> 10 |
其他惰性求值版本函数
1 | itertools.imap(pow, (1,2,3),(1,2,3)) | as_list # map的惰性版本 >>> [1, 4, 27] |
自定义Pipe管道函数
1 | from pipe import * |