利用Fn.py庫在Python中進行函數(shù)式編程
盡管Python事實上并不是一門純函數(shù)式編程語言,但它本身是一門多范型語言,并給了你足夠的自由利用函數(shù)式編程的便利。函數(shù)式風格有著各種理論與實際上的好處(你可以在Python的文檔中找到這個列表):
- 形式上可證
- 模塊性
- 組合性
- 易于調(diào)試及測試
雖然這份列表已經(jīng)描述得夠清楚了,但我還是很喜歡Michael O.Church在他的文章“函數(shù)式程序極少腐壞(Functional programs rarely rot)”中對函數(shù)式編程的優(yōu)點所作的描述。我在PyCon UA 2012期間的講座“Functional Programming with Python”中談?wù)摿嗽赑ython中使用函數(shù)式方式的內(nèi)容。我也提到,在你嘗試在Python中編寫可讀同時又可維護的函數(shù)式代碼時,你會很快發(fā)現(xiàn)諸多問題。
fn.py類庫就是為了應(yīng)對這些問題而誕生的。盡管它不可能解決所有問題,但對于希望從函數(shù)式編程方式中獲取最大價值的開發(fā)者而言,它是一塊“電池”,即使是在命令式方式占主導地位的程序中,也能夠發(fā)揮作用。那么,它里面都有些什么呢?
Scala風格的Lambda定義
在Python中創(chuàng)建Lambda函數(shù)的語法非常冗長,來比較一下:
Python
map(lambda x: x*2, [1,2,3])
Scala
List(1,2,3).map(_*2)
Clojure
(map #(* % 2) '(1 2 3))
Haskell
map (2*) [1,2,3]
受Scala的啟發(fā),F(xiàn)n.py提供了一個特別的_對象以簡化Lambda語法。
from fn import _ assert (_ + _)(10, 5) = 15 assert list(map(_ * 2, range(5))) == [0,2,4,6,8] assert list(filter(_ < 10, [9,10,11])) == [9]
除此之外還有許多場景可以使用_:所有的算術(shù)操作、屬性解析、方法調(diào)用及分片算法。如果你不確定你的函數(shù)具體會做些什么,你可以將結(jié)果打印出來:
from fn import _ print (_ + 2) # "(x1) => (x1 + 2)" print (_ + _ * _) # "(x1, x2, x3) => (x1 + (x2 * x3))"
流(Stream)及無限序列的聲明
Scala風格的惰性求值(Lazy-evaluated)流。其基本思路是:對每個新元素“按需”取值,并在所創(chuàng)建的全部迭代中共享計算出的元素值。Stream對象支持<<操作符,代表在需要時將新元素推入其中。
惰性求值流對無限序列的處理是一個強大的抽象。我們來看看在函數(shù)式編程語言中如何計算一個斐波那契序列。
Haskell
Clojure
Scala
0 #:: 1 #:: fibs.zip(fibs.tail).map{case (a,b) => a + b}
現(xiàn)在你可以在Python中使用同樣的方式了:
from fn import Stream from fn.iters import take, drop, map from operator import add f = Stream() fib = f << [0, 1] << map(add, f, drop(1, f)) assert list(take(10, fib)) == [0,1,1,2,3,5,8,13,21,34] assert fib[20] == 6765 assert list(fib[30:35]) == [832040,1346269,2178309,3524578,5702887]
蹦床(Trampolines)修飾符
fn.recur.tco是一個不需要大量??臻g分配就可以處理TCO的臨時方案。讓我們先從一個遞歸階乘計算示例開始:
def fact(n): if n == 0: return 1 return n * fact(n-1)
這種方式也能工作,但實現(xiàn)非常糟糕。為什么呢?因為它會遞歸式地保存之前的計算值以算出最終結(jié)果,因此消耗了大量的存儲空間。如果你對一個很大的n值(超過了sys.getrecursionlimit()的值)執(zhí)行這個函數(shù),CPython就會以此方式失敗中止:
>>> import sys >>> fact(sys.getrecursionlimit() * 2) ... many many lines of stacktrace ... RuntimeError: maximum recursion depth exceeded
這也是件好事,至少它避免了在你的代碼中產(chǎn)生嚴重錯誤。
我們?nèi)绾蝺?yōu)化這個方案呢?答案很簡單,只需改變函數(shù)以使用尾遞歸即可:
def fact(n, acc=1): if n == 0: return acc return fact(n-1, acc*n)
為什么這種方式更佳呢?因為你不需要保留之前的值以計算出最終結(jié)果??梢栽赪ikipedia上查看更多尾遞歸調(diào)用優(yōu)化的內(nèi)容。可是……Python的解釋器會用和之前函數(shù)相同的方式執(zhí)行這段函數(shù),結(jié)果是你沒得到任何優(yōu)化。
fn.recur.tco為你提供了一種機制,使你可以使用“蹦床”方式獲得一定的尾遞歸優(yōu)化。同樣的方式也使用在諸如Clojure語言中,主要思路是將函數(shù)調(diào)用序列轉(zhuǎn)換為while循環(huán)。
from fn import recur @recur.tco def fact(n, acc=1): if n == 0: return False, acc return True, (n-1, acc*n)
@recur.tco是一個修飾符,能將你的函數(shù)執(zhí)行轉(zhuǎn)為while循環(huán)并檢驗其輸出內(nèi)容:
- (False, result)代表運行完畢
- (True, args, kwargs)代表我們要繼續(xù)調(diào)用函數(shù)并傳遞不同的參數(shù)
- (func, args, kwargs)代表在while循環(huán)中切換要執(zhí)行的函數(shù)
函數(shù)式風格的錯誤處理
假設(shè)你有一個Request類,可以按照傳入其中的參數(shù)名稱得到對應(yīng)的值。要想讓其返回值格式為全大寫、非空并且去除頭尾空格的字符串,你需要這樣寫:
class Request(dict): def parameter(self, name): return self.get(name, None) r = Request(testing="Fixed", empty=" ") param = r.parameter("testing") if param is None: fixed = "" else: param = param.strip() if len(param) == 0: fixed = "" else: fixed = param.upper()
額,看上去有些古怪。用fn.monad.Option來修改你的代碼吧,它代表了可選值,每個Option實例可代表一個Full或者Empty(這點也受到了Scala中Option的啟發(fā))。它為你編寫長運算序列提供了簡便的方法,并且去掉除了許多if/else語句塊。
from operator import methodcaller from fn.monad import optionable class Request(dict): @optionable def parameter(self, name): return self.get(name, None) r = Request(testing="Fixed", empty=" ") fixed = r.parameter("testing") .map(methodcaller("strip")) .filter(len) .map(methodcaller("upper")) .get_or("")
fn.monad.Option.or_call是個便利的方法,它允許你進行多次調(diào)用嘗試以完成計算。例如,你有一個Request類,它有type,mimetype和url等幾個可選屬性,你需要使用最少一個屬性值以分析它的“request類型”:
from fn.monad import Option request = dict(url="face.png", mimetype="PNG") tp = Option \ .from_value(request.get("type", None)) \ # check "type" key first .or_call(from_mimetype, request) \ # or.. check "mimetype" key .or_call(from_extension, request) \ # or... get "url" and check extension .get_or("application/undefined")
其余事項?
我僅僅描述了類庫的一小部分,你還能夠找到并使用以下功能:
- 22個附加的itertools代碼段,以擴展內(nèi)置module的功能的附加功能
- 將Python 2和Python 3的迭代器(iterator)(如range,map及filtter等等)使用進行了統(tǒng)一,這對使用跨版本的類庫時非常有用
- 為函數(shù)式組合及partial函數(shù)應(yīng)用提供了簡便的語法
- 為使用高階函數(shù)(apply,flip等等)提供了附加的操作符
正在進行中的工作
自從在Github上發(fā)布這個類庫以來,我從社區(qū)中收到了許多審校觀點、意見和建議,以及補丁和修復。我也在繼續(xù)增強現(xiàn)有功能,并提供新的特性。近期的路線圖包括以下內(nèi)容:
- 為使用可迭代對象(iterable),如foldl,foldr增加更多操作符
- 更多的monad,如fn.monad.Either,以處理錯誤記錄
- 為大多數(shù)module提供C-accelerator
- 為簡化lambda arg1: lambda arg2:…形式而提供的curry函數(shù)的生成器
- 更多文檔,更多測試,更多示例代碼
相關(guān)文章
Python使用Matplotlib實現(xiàn)創(chuàng)建動態(tài)圖形
動態(tài)圖形是使可視化更具吸引力和用戶吸引力的好方法,它幫助我們以有意義的方式展示數(shù)據(jù)可視化,本文將利用Matplotlib實現(xiàn)繪制一些常用動態(tài)圖形,希望對大家有所幫助2024-02-02Python如何用str.format()批量生成網(wǎng)址(豆瓣讀書為例)
這篇文章主要介紹了Python如何用str.format()批量生成網(wǎng)址(豆瓣讀書為例),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09Python實現(xiàn)PS圖像調(diào)整之對比度調(diào)整功能示例
這篇文章主要介紹了Python實現(xiàn)PS圖像調(diào)整之對比度調(diào)整功能,結(jié)合實例形式分析了Python實現(xiàn)PS圖像對比度調(diào)整的原理、實現(xiàn)方法及相關(guān)操作技巧,需要的朋友可以參考下2018-01-01