Python進階:函數式編程實例(附代碼)

上篇文章「幾個小例子告訴你, 一行Python代碼能幹哪些事 -- 知乎專欄」中用到了一些列表解析、生成器、map、filter、lambda、zip等表達形式,這就涉及到了Python中關於函數式編程(functional programming)的語法、函數等。這裡我們就根據一些實例,聊聊Python中的函數式編程。

先附上維基百科中關於函數式編程的解釋:

函數式編程,或稱函數程序設計,又稱泛函編程,是一種編程范型,它將電腦運算視為數學上的函數計算,並且避免使用程序狀態以及易變對象。函數編程語言最重要的基礎是λ演算。而且λ演算的函數可以接受函數當作輸入(引數)和輸出(傳出值)。

比起命令式編程,函數式編程更強調程序執行的結果而非執行的過程,倡導利用簡單的執行單元讓計算結果不斷漸進,逐層推導複雜的運算,而不是設計一個複雜的執行過程。

關於函數式編程的好處、原則等等,我們就不聊了,大家可以自行谷歌。

Python作為一門腳本語言,也具有一些函數式編程的思想,主要體現在下面幾個方面:

  • Python的一些語法,比如lambda、列表解析、字典解析、生成器、iter等
  • Python的一些內置函數,包括map、reduce、filter、all、any、enumerate、zip等
  • Python的一些內置模塊,比如 itertools、functools 和 operator模塊等
  • Python的一些第三方庫,比如fn.py,toolz等

這裡我們就根據這四個方面,分別聊一聊各自的語法、實例等。

Python函數式編程之語法篇

(1)列表解析,將range(5)的每個元素進行平方:

a_list = [item**2 for item in range(5)]print(a_list) # [0, 1, 4, 9, 16]

(2)字典解析,將range(5)的每個元素進行平方並作為value,key為一個指示:

a_dict = {"%d^2" % item: item**2 for item in range(5)}print(a_dict) # {"3^2": 9, "2^2": 4, "1^2": 1, "0^2": 0, "4^2": 16}

(3)生成器:和列表解析比較類似,區別在於它的結果是generator object,不能直接列印,但可以進行迭代(使用next函數、放入for循環等)。

a_generator = (item**2 for item in range(5))print(a_generator) # <generator object <genexpr> at 0x10e366570>print(next(a_generator)) # 0print(next(a_generator)) # 1

(4)iter函數和next函數:一個list類型不能使用next函數,需將其轉化為iterator類型。

a_list_generator = iter(a_list)print(next(a_list_generator)) # 0print(type(a_list), type(a_list_generator)) # <class "list"> <class "list_iterator">

(5)lambda表達式,即定義一些比較簡單的匿名函數,lambda表達式和map、reduce、filter等函數混合使用威力巨大。例如求x的y次方:

a_func = lambda x, y: x**y print(a_func(2, 3)) # 8

Python函數式編程之內置函數篇

這裡沒有將iter和next歸到這裡,只是個人喜好而已,大家不用在意這些細節。

(1)map函數:將一個函數應用於一個或多個可迭代對象,返回一個map object。這裡的函數可以為內置函數、operator模塊中的函數或者一個lambda函數等:

print(map(abs, range(-4, 5))) # <map object at 0x1097bd6d8>print(list(map(abs, range(-4, 5)))) # [4, 3, 2, 1, 0, 1, 2, 3, 4]print(list(map(lambda x: x**2, range(5)))) # [0, 1, 4, 9, 16]print(list(map(lambda x, y: x**y, range(1, 5), range(1, 5)))) # [1, 4, 27, 256]

(2)reduce函數:這個函數並不能直接調用,而是需要通過functools庫進行引入。該函數的作用是將一個可迭代對象中的元素進行reduce(累加等),最後一個參數可選,為初始值。

print(reduce(lambda x, y: x+y, range(10))) # 45print(reduce(lambda x, y: x+y, range(10), 100)) # 145print(reduce(lambda x, y: x+y, [[1, 2], [3, 4]], [0])) # [0, 1, 2, 3, 4]

(3)filter函數:按照字面意思理解即可,即過濾一個可迭代對象,保留為True的元素。注意第二個例子,只過濾掉0,因為即使是負數也會被判定為True。

print(filter(None, range(-4, 5))) # <filter object at 0x10c096710>print(list(filter(None, range(-4, 5)))) # [-4, -3, -2, -1, 1, 2, 3, 4]print(list(filter(lambda x: x > 0, range(-4, 5)))) # [1, 2, 3, 4]

(4)all、any函數:比較簡單,還是可以按照字面意思理解,即判定一個可迭代對象是否全為True或者有為True的。

print(all([0, 1, 2])) # Falseprint(any([0, 1, 2])) # True

(5)enumerate函數,如果你想迭代一個列表或者元組,又想知道當前迭代元素的index值,那麼enumerate就能滿足你的需求:

for index, item in enumerate(range(5)): print("%d: %d" % (index, item)) # 0: 0
1: 1
2: 2 ......

(6)zip函數,映射兩個或多個可迭代對象,組成新的可迭代對象,直接看實例:

for a, b in zip([1, 2, 3], ["a", "b", "c"]): print(a, b) # 1 a
2 b
3 c
a_dict = dict(zip([1, 2, 3], ["a", "b", "c"]))print(a_dict) # {1: "a", 2: "b", 3: "c"}

Python函數式編程之內置模塊篇

這裡主要涉及內置模塊itertools、functools和operator等。這幾個內置模塊可配合上邊的map、reduce等函數一起使用。由於涉及到的內容較多,這裡只做簡單介紹。有什麼不明白或者不清楚的地方,可以讀一下Python的官方文檔:Functional Programming Modules

(1)itertools模塊:包含創建有效迭代器的函數,可以用各種方式對數據進行循環操作,該模塊的所有函數返回的迭代器都可以與for循環語句以及其他包含迭代器的函數聯合使用。主要包含三類函數:

  • 無限迭代器,比如itertools.count(10, 2)將無限產生從10開始,step為2的數,這裡需要用break手動停止。

  • 作用於序列上的一些迭代器,比如itertools.chain(a_list, b_list)將兩個列表連起來。

  • 組合生成器,比如排列功能、組合功能等。

(2)functools模塊:定義一些高階函數,用於「act」或者「return」其他函數。前邊已經介紹過其中的一個函數reduce了。這裡再介紹一個平時常用的partial函數,其他函數大家可以參考其官方文檔。

print(int("10010", base=2)) # 18int_base_2 = partial(int, base=2) # partial可以給函數添加或更改默認參數print(int_base_2("10010")) # 18

(3)operator模塊:提供了一個與Python固有操作相對應的集合。例如operator.add(x, y)等價於表達式x+y。該模塊中函數的名字就是類的方法名。這裡可以結合map、reduce等使用:

print(reduce(lambda x, y: x+y, range(10))) # 45print(reduce(add, range(10))) # 45

Python函數式編程之第三方庫

第三方庫包括fn.py, toolz等,fn.py地址:GitHub - kachayev/fn.py: Functional programming in Python,toolz地址:pytoolz/toolz: A functional standard library for Python。兩個第三方庫的很多功能都和Python內置的語法、函數類似,這裡就不一一解釋了。這裡就舉一個fn.py的例子,其他的大家可以去參考他的代碼或者文檔:

from fn import _add_func_1 = (_ + 2)print(add_func_1(1)) # 3add_func_2 = (_ + _ * _)print(add_func_2(1, 2, 3)) # 7

以上大概就是Python函數式編程的基本語法、函數、思想等,有什麼問題可以在評論中指出,大家一起討論,一起進步。對了,Python中的裝飾器應該也算是函數式編程的一種用法,有時間專門寫一篇關於Python裝飾器的文章。

按照慣例,文章中出現的所有代碼,均已上傳到Github:GitHub - xianhu/LearnPython: 以擼代碼的形式學習Python

=============================================================

作者主頁:笑虎(Python愛好者,關注爬蟲、數據分析、數據挖掘、數據可視化等)

作者專欄主頁:擼代碼,學知識 - 知乎專欄

作者GitHub主頁:擼代碼,學知識 - GitHub

歡迎大家拍磚、提意見。相互交流,共同進步!

==============================================================


推薦閱讀:

國內有沒有學校講Lisp或者函數式編程呢?
函數式編程必讀論文有哪些?
haskell中 foldr 與foldl的差別?
Clojure如何保證函數式編程的純度(purely functional programming)?
關於函數式編程的思考?

TAG:Python | 函数式编程 | 数据分析 |