Python 是個支援多重典範的語言,雖然不鼓勵,不過想在 Python 中進行一些函數式程式設計(Functional programming),基本上也有一些支援的元素。
惰性求值
在〈Python 3 Tutorial 第四堂(1)資料處理函式〉談到的 zip
、filter
、map
,其實都是函數式程式設計中必備的基本元素,而在 Python 3.x 中,map
、filter
傳回的實例並不是 list
,而是個產生器(Generator),具有惰性求值的特性,這也是函數式語言中常見的特性之一。
再來看一次〈Python 3 Tutorial 第四堂(1)資料處理函式〉中的例子:
lt = ['Justin', 'caterpillar', 'openhome']
print(list(filter(lambda ele: len(ele) > 6, lt)))
print(list(filter(lambda ele: 'i' in ele, lt)))
print(list(map(lambda ele: ele.upper(), lt)))
print(list(map(lambda ele: len(ele), lt)))
單看上頭這個程式,感覺你並沒有改變程式中 lt
的狀態,也就是沒有任何具有副作用的操作,而這就像是函數式程式設計的風格。
List 包含式
實際上,你也可以使用 for
包含式來進行類似的操作:
lt = ['Justin', 'caterpillar', 'openhome']
print([ele for ele in lt if len(ele) > 6])
print([ele for ele in lt if 'i' in ele])
print([ele.upper() for ele in lt])
print([len(ele) for ele in lt])
上頭的 for
包含式會產生 list
的結果,實際上,你也可以將 for
包含式兩側的 []
改 ()
來得到一個產生器,,像是將 [ele for ele in lt if len(ele) > 6]
改為 (ele for ele in lt if len(ele) > 6)
,同樣也就會具有惰性的效果。
正如〈Python 3 Tutorial 第二堂(3)容器、流程、for 包含式〉中談過的,for
包含式其實概念是來自函數式程式設計中的 List 包含式(List comprehension),例如數學式 S = { 2 . x | x ∈ N, x ≦ 10}
,用純函數式語言 Haskell 是寫為 [2 * x | x <- N, x <= 10]
,而在 Python 中就是寫成 [2 * x for x in N if x <= 10]
。
filter
、map
能做得到的,for
包含式基本上都做得到,大多情況下,for
包含式比較常見,不過有時 lambda
透過適當的命名,改用 filter
、map
會有比較好的可讀性。例如:
lt = ['Justin', 'caterpillar', 'openhome']
print(list(map(len, lt)))
看到了嗎?上頭直接將 len
函式傳入,可以將函式作為值傳遞的方便性就是如此,當函式本身具有名稱時,就可以增加可讀性。
實際上,函式可作為值傳遞、filter
、map
、for
包含式、惰性求值等,這些都是函數式程式設計中常見且極為基本的設計元素,你不用做完全的純函數式設計,只要在 Python 中適當地採用,在可讀性與效率方面,也許都會有所增益。
關於 reduce
在 Python 2.x 中,__builtin__
中有個 reduce
函式,有時在別的語言中會被稱為 foldLeft,它其實代表了一種高度抽象化後的流程重用,只要是打算從清單中求值的需求,基本上都可以使用它。不過,reduce
的概念比較抽象而不易閱讀理解,通常會基於 reduce
打造更具體的函式,因此,在 Python 3.x 中,reduce
不再位於 __builtin__
模組,而被移至 funtools
模組了。
以下對於 reduce
的介紹,是給你想更進一步瞭解函數式程式設計時使用,若不想理解,暫時跳過也沒關係。
舉例來說,如果你想要知道 [1, 2, 3, 4, 5]
的加總,雖然可以直接撰寫迴圈來求值,不過,也可以撰寫為 reduce(lambda sum, elem: sum + elem, [1, 2, 3, 4, 5], 0)
來求值,試試在 Python 互動環境中鍵入,結果會是 15。
>>> import functools
>>> functools.reduce(lambda sum, elem: sum + elem, [1, 2, 3, 4, 5], 0)
15
>>>
初學者會有點難懂 reduce
的原理,可以藉由這段動畫來理解,你也就會知道,為什麼它又在別的語言中,被稱為 foldLeft:
reduce
接受的 lambda
部份,改為一個具體名稱的 add
函式,那麼就可以寫為 為 reduce(add, [1, 2, 3, 4, 5], 0)
,配合上圖,reduce
的運作就像是折紙,從 0 開始,每折一次就與藍色數字進行一次 add,折完後的結果就是加總值。
這個 reduce
是極為通用的函式,可以有一百萬種用法,只要你想從某個清單中求值,都可以使用 reduce
,只要你依需求給 reduce
要處理的函式與初值。
想要進一步知道 reduce
的原理,可以看看〈List 處理模式〉中的說明。
練習 7:使用 reduce
使用 reduce
與 for
包含式,將以下的範例進行重構,目標是清除所有顯式的迴圈流程:
def ascending(a, b): return a - b
def descending(a, b): return -ascending(a, b)
# selection sort
def sorted(xs, compare = ascending):
return [] if not xs else __select(xs, compare)
def __select(xs, compare):
selected = xs[0]
for elem in xs[1:]:
if compare(elem, selected) < 0:
selected = elem
remain = []
selected_list = []
for elem in xs:
if elem != selected:
remain.append(elem)
else:
selected_list.append(elem)
return xs if not remain else selected_list + __select(remain, compare)
print(sorted([2, 1, 3, 6, 5]))
print(sorted([2, 1, 3, 6, 5], descending))
如果你能完成練習 7,你已經做了一次函數式程式設計了。無論如何,藉由這個練習,瞭解到 Python 可進行多重典範設計,只要你願意的話,函數式是可以的設計之一。