函數式 Python 中的 Pipe 與 itertools

一、迭代器與管道函數式編程簡介

可迭代器(iterable),不只限於list/str等,還包括任何包含有yield關鍵字的函數,後者未必有規律的迭代特徵。標準庫中的itertools包提供了更加靈活的產生迭代器的工具,這些工具的輸入大都是已有的迭代器函數的封裝,而且itertools給出的函數都是針對廣義迭代器而言。而len()等函數是針對狹義迭代器,即sequence(i.e. str, list, tuple)而言的。html

之內置函數range()爲例,執行結果會是一次性計算好整個序列。這對於很長的序列來講會比較耗時,甚至帶來性能問題。於是,python還提供了內置函數,提供了惰性求值版本,那就是xrange()。它利用yield特性,第一次執行時僅僅返回迭代器,不到用時是不會求值的。java

實際上,itertools提供的函數都是惰性的,而且給原內置函數都重寫了惰性版本。如imap()對於內置的map()。python

擴展庫Pipe則對內置函數和部分itertools進行了封裝,提供了相似unix bash下的管道式調用風格,更接近人類從左到右的閱讀習慣,使得代碼更加優雅。其餘動態語言,如ruby, c#-lambda java8-lambda也都提供了相似的鏈式調用形式。編程

另外,也提供了@Pipe裝飾器,能夠很是方便地擴展出本身的管道函數,或者繼續封裝其餘itertools中的有用函數。c#

這裏是Pipe官方給出的例子,用管道函數式編程解出 https://projecteuler.net/ 中的三道題目:ruby

# Find the sum of all the multiples of 3 or 5 below 1000.
euler1 = (itertools.count() | select(lambda x: x * 3) | take_while(lambda x: x < 1000) | add) \
       + (itertools.count() | select(lambda x: x * 5) | take_while(lambda x: x < 1000) | add) \
       - (itertools.count() | select(lambda x: x * 15) | take_while(lambda x: x < 1000) | add)
assert euler1 == 233168

# Find the sum of all the even-valued terms in Fibonacci which do not exceed four million.
euler2 = fib() | where(lambda x: x % 2 == 0) | take_while(lambda x: x < 4000000) | add
assert euler2 == 4613732

# Find the difference between the sum of the squares of the first one hundred natural numbers and the square of the sum.
square = lambda x: x * x
euler6 = square(itertools.count(1) | take(100) | add) - (itertools.count(1) | take(100) | select(square) | add)
assert euler6 == 25164150

注意:全部惰性求值的迭代器,都是隻能求值一次的,若是再次求值會什麼也得不到,由於yield堆棧已經走到底,沒法回頭。所以,當要對惰性迭代器重複使用時,必須故意地提早將其求值展開,或者利用itertools.tee來克隆一個迭代器。bash

二、輸入輸出

"42" | stdout # 輸出到標準輸出 >>> 42

(0, 1, 2) | as_list # 求值並轉換list類型並輸出 >>> [0, 1, 2]

lineout # 行輸出到標準輸出
tee # 輸出迭代器的每一個元素,經常使用於debug。不一樣於itertools.tee!
as_tuple # 轉換tuple類型並輸出
as_dict # 轉換dict類型並輸出
netcat # 發送網絡請求並讀取響應
netwrite # 發送網絡請求但不讀取響應

三、數學運算

[1, 2, 3, 4] | concat("#") # 鏈接字符串 >>>'1#2#3#4'

average # 求平均
count # 求長度或計數。相似len,但適用於全部迭代器
add # 求和,同sum
max # 求最大值,同max
min # 求最小值,同min
any # 只要有一個元素知足條件就返回真,相似any,但適用於全部迭代器
all # 若是全部元素都知足條件才返回真,相似all,但適用於全部迭代器

四、組合工具

(1, 2, 3, 4, 5, 6, 7, 8, 9) \
            | groupby(lambda x: x % 2 and "Even" or "Odd")
            | select(lambda x: "%s : %s" % (x[0], (x[1] | concat(', '))))
            | concat(' / ') # 將key函數做用於原循環器的各個元素, 根據key函數結果,將擁有相同函數結果的元素分到一個新的迭代器。以key函數返回結果爲鍵, 以新迭代器爲值的鍵值對序列。封裝了itertools.groupby >>> 'Even : 1, 3, 5, 7, 9 / Odd : 2, 4, 6, 8'

[[1, 2], [3, 4], [5]] | chain | concat # 封裝了itertools.chain, 將兩個可迭代器展開鏈接成爲一個。 >>> '1, 2, 3, 4, 5'

(1, 2, 3) | chain_with([4, 5], [6]) | concat # 封裝了itertools.chain, 將兩個可迭代器展開鏈接成爲一個。>>> 1, 2, 3, 4, 5, 6

[[1, 2], [[[3], [[4]]], [5]]] | traverse | concat # 遞歸地展開迭代器並鏈接成一個 >>> '1, 2, 3, 4, 5'

'abc' | permutations(2) | as_list   # 排列組合之排列,封裝了itertools.permutations, 從'abc'中挑選2元素,好比ab, ba, bc, ... (ab, ba都包含)將全部結果排序,返回爲新的迭代器。

itertools.combinations('abc', 2)   # 排列組合之組合, 從'abcd'中挑選兩個元素,好比ab, bc, ... 將全部結果排序,返回爲新的迭代器。 

itertools.combinations_with_replacement('abc', 2) # 排列組合之組合,但容許兩次選出的元素重複。即多了aa, bb, cc

itertools.product('abc', [1, 2]) | as_list   # 多個迭代器的笛卡爾積。至關於嵌套循環 >>> [('a', 1), ('a', 2), ('b', 1), ('b', 2), ('c', 1), ('c', 2)]

五、產生無限序列

itertools.count(5, 2)     # 從5開始的整數循環器,每次增長2,即5, 7, 9, 11, 13, 15 ...步長默認爲1

itertools.cycle('abc')    # 重複序列的元素,即a, b, c, a, b, c ...

itertools.repeat(10, 5)   # 重複元素10,共重複5次,即10,10,10,10,10。不寫次數的話,默認無窮循環。

六、序列運算

xrange(5) | aggregate(lambda x, y: x + y, initializer=0) # 同內置reduce函數 >>> 10

[5, -4, 3, -2, 1] | sort(key=abs) | concat # 排序, 同sorted。>>> '1, -2, 3, -4, 5'

[1, 2, 3] | select(lambda x: x * x) | concat # 做用同map/imap, >>> '1, 4, 9'

[1, 2, 3] | where(lambda x: x % 2 == 0) | concat # 做用同filter/ifilter >>> '2'

[1, 2, 3, 4] | take_while(lambda x: x < 3) | concat # 不斷取出元素,直到運算爲假,封裝了itertools.takewhile, >>> '1, 2'

[1, 2, 3, 4] | skip_while(lambda x: x < 3) | concat # 不斷跳過元素,直到運算爲假,封裝了itertools.dropwhile >>>  '3, 4'

(1, 2, 3, 4, 5, 6, 7, 8, 9) \
            | izip([9, 8, 7, 6, 5, 4, 3, 2, 1]) \
            | concat # 封裝了zip的惰性版本,即itertools.izip,縱向合併兩個序列 >>> '(1, 9), (2, 8), (3, 7), (4, 6), (5, 5), (6, 4), (7, 3), (8, 2), (9, 1)'

(1, 2, 3, 4, 5, 6, 7, 8, 9) | islice(2, 8, 2) | concat # 封裝了slice的惰性版本,即itertools.islice,切片器 >>> '3, 5, 7'

itertools.compress('ABCD', [1, 1, 1, 0])  # 根據[1, 1, 1, 0]的真假值狀況,選擇第一個參數'ABCD'中的元素。 >>> A, B, C

take # 取出n個元素
skip # 跳過n個元素
first # 取出第一個元素
tail # 取出倒數n個元素
reverse # 逆序,同reversed
itertools.tee # 克隆出n個相同的迭代器

七、其餘惰性求值版本函數

itertools.imap(pow, (1,2,3),(1,2,3)) | as_list # map的惰性版本 >>> [1, 4, 27]

itertools.starmap(pow, ((2,3),(2,3))) | as_list # map的惰性版本,可是對sequence中每一個元組來執行函數 >>> [8, 8]

itertools.ifilter # filter的惰性版本
itertools.ifilterfalse # filter的惰性反值版本
itertools.izip # zip的惰性版本
itertools.izip_longest # slice的惰性版本

八、自定義Pipe管道函數

from pipe import *

select = Pipe(lambda iterable, pred: (pred(x) for x in iterable))

# OR

@Pipe
def first(iterable, default=None):
    try:
        return next(iter(iterable))
    except StopIteration:
        return default

九、Refer:

[1] 函數式Python中的Pipe與itertools網絡

http://www.jackyshen.com/2015/08/31/pipe-and-itertools-in-functional-python/
異步

[2] Python標準庫13 循環器 (itertools)async

http://www.cnblogs.com/vamei/p/3174796.html

[3] Python:itertools模塊

http://www.cnblogs.com/cython/articles/2169009.html

[4] 利用python yielding建立協程將異步編程同步化

http://www.jackyshen.com/2015/05/21/async-operations-in-form-of-sync-programming-with-python-yielding/

相關文章
相關標籤/搜索