利用Fn.py庫在Python中進行函數式編程

來源:互聯網
上載者:User
儘管Python事實上並不是一門純函數式程式設計語言,但它本身是一門多範型語言,並給了你足夠的自由利用函數式編程的便利。函數式風格有著各種理論與實際上的好處(你可以在Python的文檔中找到這個列表):

  • 形式上可證
  • 模組性
  • 組合性
  • 易於調試及測試

雖然這份列表已經描述得夠清楚了,但我還是很喜歡Michael O.Church在他的文章“函數式程式極少腐壞(Functional programs rarely rot)”中對函數式編程的優點所作的描述。我在PyCon UA 2012期間的講座“Functional Programming with Python”中談論了在Python中使用函數式方式的內容。我也提到,在你嘗試在Python中編寫可讀同時又可維護的函數式代碼時,你會很快發現諸多問題。

fn.py類庫就是為了應對這些問題而誕生的。儘管它不可能解決所有問題,但對於希望從函數式編程方式中擷取最大價值的開發人員而言,它是一塊“電池”,即使是在命令式方式佔主導地位的程式中,也能夠發揮作用。那麼,它裡面都有些什麼呢?
Scala風格的Lambda定義

在Python中建立Lambda函數的文法非常冗長,來比較一下:

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的啟發,Fn.py提供了一個特別的_對象以簡化Lambda文法。

from fn import _assert (_ + _)(10, 5) = 15assert list(map(_ * 2, range(5))) == [0,2,4,6,8]assert list(filter(_ < 10, [9,10,11])) == [9]

除此之外還有許多情境可以使用_:所有的算術操作、屬性解析、方法調用及分區演算法。如果你不確定你的函數具體會做些什麼,你可以將結果列印出來:

from fn import _ print (_ + 2) # "(x1) => (x1 + 2)" print (_ + _ * _) # "(x1, x2, x3) => (x1 + (x2 * x3))"

流(Stream)及無限序列的聲明

Scala風格的惰性求值(Lazy-evaluated)流。其基本思路是:對每個新元素“按需”取值,並在所建立的全部迭代中共用計算出的元素值。Stream對象支援<<操作符,代表在需要時將新元素推入其中。

惰性求值流對無限序列的處理是一個強大的抽象。我們來看看在函數式程式設計語言中如何計算一個斐波那契序列。

Haskell

代碼如下:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

Clojure

代碼如下:

(def fib (lazy-cat [0 1] (map + fib (rest fib))))

Scala

代碼如下:

def fibs: Stream[Int] =
0 #:: 1 #:: fibs.zip(fibs.tail).map{case (a,b) => a + b}

現在你可以在Python中使用同樣的方式了:

from fn import Stream from fn.iters import take, drop, mapfrom operator import addf = 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] == 6765assert list(fib[30:35]) == [832040,1346269,2178309,3524578,5702887]

蹦床(Trampolines)修飾符

fn.recur.tco是一個不需要大量棧空間分配就可以處理TCO的臨時方案。讓我們先從一個遞迴階乘計算樣本開始:

def fact(n):   if n == 0: return 1   return n * fact(n-1)

這種方式也能工作,但實現非常糟糕。為什麼呢?因為它會遞迴式地儲存之前的計算值以算出最終結果,因此消耗了大量的儲存空間。如果你對一個很大的n值(超過了sys.getrecursionlimit()的值)執行這個函數,CPython就會以此方式失敗中止:

>>> import sys>>> fact(sys.getrecursionlimit() * 2)... many many lines of stacktrace ...RuntimeError: maximum recursion depth exceeded

這也是件好事,至少它避免了在你的代碼中產生嚴重錯誤。

我們如何最佳化這個方案呢?答案很簡單,只需改變函數以使用尾遞迴即可:

def fact(n, acc=1):   if n == 0: return acc   return fact(n-1, acc*n)

為什麼這種方式更佳呢?因為你不需要保留之前的值以計算出最終結果。可以在Wikipedia上查看更多尾遞迴調用最佳化的內容。可是……Python的解譯器會用和之前函數相同的方式執行這段函數,結果是你沒得到任何最佳化。

fn.recur.tco為你提供了一種機制,使你可以使用“蹦床”方式獲得一定的尾遞迴最佳化。同樣的方式也使用在諸如Clojure語言中,主要思路是將函數調用序列轉換為while迴圈。

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是一個修飾符,能將你的函數執行轉為while迴圈並檢驗其輸出內容:

  • (False, result)代表運行完畢
  • (True, args, kwargs)代表我們要繼續調用函數並傳遞不同的參數
  • (func, args, kwargs)代表在while迴圈中切換要執行的函數

函數式風格的錯誤處理

假設你有一個Request類,可以按照傳入其中的參數名稱得到對應的值。要想讓其傳回值格式為全大寫、非空並且去除頭尾空格的字串,你需要這樣寫:

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的啟發)。它為你編寫長運算序列提供了簡便的方法,並且去掉除了許多if/else語句塊。

from operator import methodcallerfrom fn.monad import optionableclass 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是個便利的方法,它允許你進行多次調用嘗試以完成計算。例如,你有一個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程式碼片段,以擴充內建module的功能的附加功能
  • 將Python 2和Python 3的迭代器(iterator)(如range,map及filtter等等)使用進行了統一,這對使用跨版本的類庫時非常有用
  • 為函數式組合及partial函數應用提供了簡便的文法
  • 為使用高階函數(apply,flip等等)提供了附加的操作符

進行中中的工作

自從在Github上發布這個類庫以來,我從社區中收到了許多審校觀點、意見和建議,以及補丁和修複。我也在繼續增強現有功能,並提供新的特性。近期的路線圖包括以下內容:

  • 為使用可迭代對象(iterable),如foldl,foldr增加更多操作符
  • 更多的monad,如fn.monad.Either,以處理錯誤記錄
  • 為大多數module提供C-accelerator
  • 為簡化lambda arg1: lambda arg2:…形式而提供的curry函數的產生器
  • 更多文檔,更多測試,更多範例程式碼
  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.