前言
繼續向下看廖大教程,看到了函數語言程式設計這一節,當時是覺得沒啥用直接跳過了,這次準備要仔細看一遍了,並記錄下一些心得。
函數語言程式設計
上學期有上一門叫 ‘人工智慧’ 的課,老師強行要我們學了一個叫做 prolog 的語言,哇那感覺確實難受,思維方式完全和之前學過的不一樣,寫個漢諾塔想了半天,最後還是在網上找了段程式碼修改一下(怕被老師發現抄襲)才寫出來,貼一段出來感受一下:
1 2 3 4 5 6 7 |
hanoi(N) :- dohanoi(N, 'a', 'b', 'c'). dohanoi(0, _ , _ , _ ) :- !. dohanoi(N, A, B, C) :- N1 is N-1, dohanoi(N1, A, C, B), writeln([move, N, A-->C]), dohanoi(N1, B, A, C). |
當時是差不多弄懂了,主要是資料實在太少,debug 都無從談起,一遇上 bug 就 gg,我現在自己看也有點頭暈。不過據說 prolog 當年能和 Lisp 一爭高下,最近對 Lisp 也有點興趣,等弄完這些就去參拜一下這類函式式語言。
何謂函數語言程式設計?廖大這裡寫道:
函數語言程式設計就是一種抽象程度很高的程式設計正規化,純粹的函數語言程式設計語言編寫的函式沒有變數,因此,任意一個函式,只要輸入是確定的,輸出就是確定的,這種純函式我們稱之為沒有副作用。而允許使用變數的程式設計語言,由於函式內部的變數狀態不確定,同樣的輸入,可能得到不同的輸出,因此,這種函式是有副作用的。
可能看完還是有些不太理解,不急,先看完這幾個小節吧。
高階函式
在數學和電腦科學中,高階函式是至少滿足下列一個條件的函式:
- 接受一個或多個函式作為輸入
- 輸出一個函式
也就是說,把函式本身當成引數傳遞,或者返回一個函式。
例如,可以像普通賦值一樣將函式賦值給變數:
1 2 3 4 5 6 7 8 9 |
>>> min(1, 2) 1 >>> f = min >>> f(1, 2) 1 >>> f <built-in function min> >>> min <built-in function min> |
也可以給函式賦值(程式碼接上):
1 2 3 4 5 6 7 8 9 10 |
>>> min = 10 >>> min(1, 2) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not callable >>> f(1, 2) 1 >>> min = f >>> min(1, 2) 1 |
還可以傳參,例如,一個計算所有數字的和的函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>>> def add(a, b): ... return a+b ... >>> def mysum(f, *l): ... a = 0 ... for i in l: ... a = f(a, i) ... return a ... >>> mysum(add, 1, 2, 3) 6 >>> mysum(add, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 55 |
當然,將這個 f 換成乘法就是計算所有數字的乘積了。
再來看看 python 內建的一些高階函式,經常會用到。
map/reduce
記得上學期上雲端計算的課程時依稀有聽到過這個詞,不過這課很水,就沒怎麼聽,在這裡看到好像發現不太一樣??
不過沒啥說的,簡單說一下每個函式的作用。
對於 map,其計算式可以看成這樣:
1 |
map(f, [x1, x2, ..., xn]) = [f(x1), f(x2), ..., f(xn)] |
對於 reduce,其計算式可以看成這樣:
1 |
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) |
廖大那裡說得很清楚啦。
filter
filter 和 map 函式類似,接受一個函式和 iterable,返回也是一個 list,不過其功能是根據函式返回值是否為 True 來判斷是否保留該值。例如:
1 2 3 4 5 |
def is_odd(n): return n % 2 == 1 list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])) # 結果: [1, 5, 9, 15] |
sorted
sorted 函式同樣是一個高階函式,對引數 key 傳遞函式可以將需要排列的序列經過 key 函式處理後再進行排序,不過不會改變序列的值,例如:
1 2 |
>>> sorted([36, 5, -12, 9, -21], key=abs) [5, 9, -12, -21, 36] |
裝飾器(decorator)
匿名函式就不說了,以後用時再仔細看吧,裝飾器我記得之前看 flask 的時候都研究了好久,這次再來複習一下。
簡單裝飾器
首先是一個簡單的裝飾器,在每次呼叫函式前列印出日誌:
1 2 3 4 5 6 7 |
import logging def log(func): def wrapper(*args, **kw): logging.warn("%s is running" % func.__name__) func(*args, **kw) return wrapper |
這就是一個極其簡單的裝飾器,如何使用它呢?我最先看到的用法是在需要裝飾的函式前新增@,但其實這是 Python 的一個語法糖,最原始的用法反而更能讓人理解,先定義一個函式 f:
1 2 3 4 |
def f(): print("in function f") f = log(f) |
這樣定義了之後,我們再呼叫 f 函式:
1 2 3 |
>>> f() WARNING:root:f is running in function f |
使用 @log 的結果與其一樣,其實@符號作為裝飾器的語法糖,與前面的賦值語句具有相同的功能,使程式碼看起來更簡潔明瞭,避免再一次賦值操作,就像下面這樣:
1 2 3 |
@log def f(): print("in function f") |
含引數的裝飾器
有時候我們還需要向裝飾器中傳入引數,例如,狀態,層次等資訊,只需要在 wrapper 函式外再’包裹’一層函式,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import logging def log(level): def decorator(func): def wrapper(*args, **kw): logging.warn("%s is running at level %d" % (func.__name__, level)) return func(*args, **kw) return wrapper return decorator @log(2) def f(): print("in function f") >>> f() WARNING:root:f is running at level 2 in function f |
進一步理解
為了再進一步理解裝飾器,我們可以列印出函式 f 的 name 屬性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#對於不加裝飾器的 f,其 name 不變 >>> def f(): ... print("in function f") ... >>> f.__name__ 'f' #對於新增裝飾器的函式,其 name 改變了 >>> @log ... def f(): ... print("in function f") ... >>> f.__name__ 'wrapper' |
聯絡到最前面的裝飾器賦值語句,就可以大致明白髮生了什麼:f = log(f)
使得 f 指向修改為 log(f) 的返回值,即 wrapper 函式。每次執行原函式 f 時,則會呼叫 wrapper 函式,在我們這個例子中,則是先列印日誌,然後執行原函式 f。
不過這樣有一個問題,這樣使得原函式 f 的元資訊被替換了,關於 f 的許多資訊消失不見,這是很難令人接受的,不過好在我們有 functools 模組,修改函式為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import functools import logging def log(func): functools.wraps(func) def wrapper(*args, **kw): logging.warn("%s is running" % func.__name__) func(*args, **kw) return wrapper >>> @log ... def f(): ... print("in function f") ... >>> f.__name__ 'f' |
另外,還可以對同一個函式新增多個裝飾器:
1 2 3 4 5 6 7 8 9 |
<a href="http://www.jobbole.com/members/a58797">@a</a> @b <a href="http://www.jobbole.com/members/522c">@c</a> def f (): # 等價於 f = a(b(c(f))) |
總結
關於函數語言程式設計我也不是很瞭解,這裡只是大概瞭解了一下其概念吧,平時肯定還是使用指令式程式設計用得多。不過有語言是純函式式語言,例如 Haskell 或 Lisp,學習它們會使得人開啟一種新思路。
以上~