Python 函數式編程入門教程
函數式編程源自於數學理論,它似乎也更適用於數學計算相關的場景,因此本文以一個簡單的數據處理問題為例,逐步介紹 Python 函數式編程從入門到走火入魔的過程。
一、什麼是函數式編程?
函數:function
函數式:functional,是一種編程範式
二、函數式編程的特點:
1)把計算視為函數而非指令
2)純函數式編程:不需要變量,沒有副作用,測試簡單
3)支持高階函數,代碼簡潔
三、Python支持的函數式編程
1)Python不是純函數式編程:允許有變量
2)Python支持高階函數:函數也可以作為變量傳入
3)Python支持閉包:有了閉包就能返回函數
4)有限度的支持你匿名函數
問題:計算 N 位同學在某份試卷的 M 道選擇題上的得分(每道題目的分值不同)。
首先來生成一組用於計算的偽造數據:
四、入門
首先來看常規的面向過程編程風格,我們需要遍歷每個學生,然後遍歷每個學生對每道題目的答案並與真實答案進行比較,然後將正確答案的分數累計:
如果你覺得上面的代碼非常直觀且合乎邏輯,那說明你已經習慣按照計算機的思維模式進行思考了。通過創建嵌套兩個 for 循環來遍歷所有題目答案的判斷和評分,這完全是為計算機服務的思路,雖然說 Python 中的 for 循環已經比 C 語言更進了一步,通常不需要額外的狀態變量來記錄當前循環的次數,但有時候也不得不使用狀態變量,如上例中第二個循環中比較兩個列表的元素。函數式編程的一大特點就是儘量拋棄這種明顯循環遍歷的做法,而是把注意集中在解決問題本身,一如在現實中我們批改試卷時,只需要將兩組答案並列進行比較即可:
然後再將所有正確題目的分數累加起來,即可:
from functools import reduce
reduced = reduce(lambda x, y: x + y[1][1], filtered, 0)
print(reduced)
以上是對一位學生的結果處理,接下來只需要對所有學生進行同樣的處理即可:
上面的示例通過 zip/filter/reduce/map 等函數將數據處理的方法打包應用到數據上,實現了基本的函數式編程操作。但是如果你對函數式有更深入的瞭解,你就會發現上面的 cal 方法中使用了全局變量 QUIZE,這會導致在相同輸入的條件下,函數可能產生不同的輸出,這是 FP 的大忌,因此需要進行整改:
如此藉助閉包(Closure)的方法,就可以維持純淨的 FP 模式啦!
函數式編程的一大優勢就是Immutable Data(數據不可變),就是不依賴於外部的數據,而且也不改變外部數據的值,這種思想可以大大減少我們代碼的Bug,而且函數式編程也支持我們像使用變量一樣使用函數。Python作為面嚮對象語言,也提供了對於函數式編程的支持,雖然並不是那麼純粹,而且也不支持尾遞歸優化。
1.lambda的使用
lambda即匿名函數,合理地使用lambda不僅可以減少我們的代碼量,而且也可以更好地描繪代碼邏輯,比如現在我們有下面這樣一個函數。
>>>deff(x):
...returnx+x
# 調用這個函數
>>>f(2)
4
這個函數如果我們用lamda改寫的話,只要一行代碼就夠了。# lambda後面的x表示lambda函數要接收的參數,x + x表示lambda函數所要返回的值
>>>f=lambdax:x+x
# 可以看到f現在也是一個函數對象
>>>f
>
# 調用lambda函數
>>>f(2)
4
python中map()函數
map(function, iterable)接收兩個參數,第一個參數代表的是接收一個函數,第二個參數代表的是接收一個iteralbe類型的對象,比如list。
map函數的原理是:
1.每次從iterable中取出一個參數
2.將這個參數傳遞給我們的函數
3.然後函數返回的值加入一個list(這種說法不準確,只是為了幫助大家理解,後面我會解釋)。等所有的iterable對象遍歷完,map就把這個list返回給我們的調用者。下面我們直接通過實例來了解一下map的用法。
python中reduce()函數
reduce()函數也是Python內置的一個高階函數。reduce()函數接收的參數和 map()類似,一個函數 f,一個list,但行為和 map()不同,reduce()傳入的函數 f 必須接收兩個參數,reduce()對list的每個元素反覆調用函數f,並返回最終結果值。
例如:
請利用recude()來求積:
輸入:[2, 4, 5, 7, 12]
輸出:245712的結果
def prod(x, y):
return x*y
print reduce(prod, [2, 4, 5, 7, 12])
輸出:3360
python中filter()函數
和map/reduce類似,filter(function, iterable)一次也接收兩個參數,一個參數是函數,另外一個參數是iterable對象,從名字也可以看出,filter用於過濾iterble對象,比如說list(列表)。
它的原理是每次從iterable對象中取出一個元素作用於我們的function,如果function返回True就保留該元素,如果返回False就刪除該元素。下面我們通過一個實例來看一下filter的用法。
定義一個函數,如果接收的字符s為空,那麼返回False,如果為非空,那麼返回True
>>>function=lambdas:sands.strip()
>>>iterable=['AJ',' ','Stussy','','CLOT','FCB',None]
>>>filter(function,iterable)
>>>list(filter(function,iterable))
['AJ','Stussy','CLOT','FCB']
python中自定義排序函數
Python內置的 sorted()函數可對list進行排序:
sorted([36, 5, 12, 9, 21])
輸出:[5, 9, 12, 21, 36]
但 sorted()也是一個高階函數,它可以接收一個比較函數來實現自定義排序,比較函數的定義是,傳入兩個待比較的元素 x, y,如果 x 應該排在 y 的前面,返回 -1,如果 x 應該排在 y 的後面,返回 1。如果 x 和 y 相等,返回 0。
因此,如果我們要實現倒序排序,只需要編寫一個reversed_cmp函數:
def reversed_cmp(x, y):
if x > y:
return -1
if x < y:
return 1
return 0
這樣,調用 sorted() 並傳入 reversed_cmp 就可以實現倒序排序:
sorted([36, 5, 12, 9, 21], reversed_cmp)
輸出:[36, 21, 12, 9, 5]
sorted()也可以對字符串進行排序,字符串默認按照ASCII大小來比較:
sorted(['bob', 'about', 'Zoo', 'Credit'])
輸出:['Credit', 'Zoo', 'about', 'bob']
'Zoo'排在'about'之前是因為'Z'的ASCII碼比'a'小。
示例:
對字符串排序時,有時候忽略大小寫排序更符合習慣。請利用sorted()高階函數,實現忽略大小寫排序的算法。
輸入:['bob', 'about', 'Zoo', 'Credit']
輸出:['about', 'bob', 'Credit', 'Zoo']
走火入魔
走火(fn.py)
也許看了上面的 FP 寫法,你還是覺得挺囉嗦的,並沒有達到你想象中的結果,這時候就需要呈上一款語法糖利器:fn.py!fn.py 封裝了一些常用的 FP 函數及語法糖,可以大大簡化你的代碼!
pip install fn
首先從剛剛的閉包開始,我們可以用更加 FP 的方法來解決這一問題,稱為柯里化,簡單來說就是允許接受多個參數的函數可以分次執行,每次只接受一個參數:
應用到上面的 cal 方法中:
在 FP 中數據通常被看作是一段數據流在一串函數的管道中傳遞,因此上面的reduce和filter其實可以合併:
reduce(lambda x, y: x + y[1][1], filter(lambda x: x[0] == x[1][0], zip(student.ans, quize)), 0)
雖然更簡略了,但是這樣會大大降低代碼的可讀性(這也是 FP 容易遭受批評的一點),為此 fn 提供了更高級的函數操作工具:
入魔(Hy)
如果你覺得上面的代碼已經足夠魔性到看起來不像是 Python 語言了,然而一旦接受了這樣的語法設定感覺也還挺不錯的。如果你興沖沖地拿去給 Lisp 或 Haskell 程序員看,則一定會被無情地鄙視,於是你痛定思痛下定決心繼續挖掘 Python 函數式編程的奧妙,那麼恭喜你,組織歡迎你的加入:Hail Hydra!
哦不對,說漏了,是Hi Hy!
Hy 是基於 Python 的 Lisp 方言,可以與 Python 代碼進行完美互嵌(如果你更偏好 PyPy,同樣也有類似的Pixie),
除此之外你也可以把它當做一門獨立的語言來看待,它有自己的解釋器,可以當做獨立的腳本語言來使用:
pip install git+https://github.com/hylang/hy.git
首先來看一下它的基本用法,和 Python 一樣,安裝完之後可以通過 hy 命令進入 REPL 環境:
或者當做命令行腳本運行:
#! /usr/bin/env hy
(print "I was going to code in Python syntax, but then I got Hy.")
保存為 awesome.hy:
chmod +x awesome.hy
./awesome.hy
接下來繼續以上面的問題為例,首先可以直接從 Python 代碼中導入:
如果覺得不放心,還可以直接調用最開始定義的方法將結果進行比較:
;; 假設最上面的 normal 方法保存在 fun.py 文件中
(import fun)
(.normal fun students quize)
還有很多包括視頻我就不一一截圖了,需要這些資料的可以先關注小編,轉發評論,私信小編回覆006、008即可領取資料。誠信小編!!!
閱讀更多 雯Ping 的文章