愛江山更愛美人,枕邊佳人,你值得擁有。
1 美人心計
今日向大家介紹後花園中的3位美人,分別是命令氏、物件導向氏、函式氏。
- 命令氏,妃,賢妻良母,夫唱婦隨,和她在一起四字可形容,簡單粗暴;
- 物件導向氏,嬪,聰明伶俐,足智多謀,心眼一個接著一個,省了我不少事情;
- 函式氏,貴人,零零後,八面玲瓏,火星語亂飛,女孩的心思你別猜。
她們國色天香,楚楚動人,大概是下面這個樣子:
2 求和問題
我曾遇到“求和問題”,美人傍身各施其計。問題如下:
- 求x和y的和(測試用例:x=1,y=2)
- 求x和y的平方的和(測試用例:x=1,y=2)
- 求x的m次方和y的n次方的和(測試用例:x=1,y=2,m=3,n=4)
2.1 命令氏
命令氏的方法簡單粗暴,可行且有效,但是有些笨拙。定義3個函式sum1
、sum2
、sum3
,依次解決上面的3個問題。
1 2 3 4 5 6 7 8 9 10 11 12 |
def sum1(x, y): return x + y def sum2(x, y): return x**2 + y**2 def sum3(x, y, m, n): return x**m + y**n sum1(1, 2) # 3 sum2(1, 2) # 5 sum3(1, 2, 3, 4) # 17 |
2.2 物件導向氏
物件導向氏的方法要美得多,兩個字“優雅”。所以,物件導向氏一直是枕邊佳人,恩寵備至。封裝、繼承、多型是物件導向氏的特色,對於sum
函式,傳入不同的引數將會觸發不同的計算過程。放到C++裡描述,sum
可以寫成2個函式,int Sum::sum(void)
和int Sum::sum(int, int)
。
1 2 3 4 5 6 7 8 9 10 11 12 |
class Sum: def __int__(self, x, y): self.x, self.y = x, y def sum(self, m=1, n=1): return self.x**m + self.y**n s = Sum(1, 2) s.sum() # 3 s.sum(2, 2) # 5 s.sum(3, 4) # 17 |
2.3 函式氏
函式氏的方法要奇特的多,乍一看有點笨,但卻是完全不一樣的思想。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def sum(x, y, f, g): return f(x) + g(y) def f1(x): return x def f2(x): return x**2 def f3(x): return x**3 def f4(x): return x**4 sum(1, 2, f1, f1) # 3 sum(1, 2, f2, f2) # 5 sum(1, 2, f3, f4) # 17 |
解決了今日的“求和問題”,可能會遇到明天的“求差問題”,問題無窮匱也。而我們心中的困惑在於,如何確定編碼的粒度,就像做一款產品,哪些功能該加,哪些功能不該加,哪些功能暫時不加,如果某一個功能以後要加,能不能很好的融入到已有的產品中。
3 翻牌函式氏
函式氏的功勞在於抽象出了sum
的求解公式:$sum(x,y)=f(x)+g(y)$,非常有數學風啊哈。但是,方案還是有瑕疵。4個函式$f_1,f_2,f_3,f_4$分別用於計算$x_k,k \in \{1,2,3,4\}$。當我們需要計算$x_{k’},k’ \in [1,100]$怎麼辦?勞心者治人,我們可以做得更漂亮,比如這樣。
1 2 3 4 5 6 7 8 9 10 11 |
def sum(x, y, f, g): return f(x) + g(y) def generate_func(k): def func(x): return x**k return func sum(1, 2, generate_func(1), generate_func(1)) # 3 sum(1, 2, generate_func(2), generate_func(2)) # 5 sum(1, 2, generate_func(3), generate_func(4)) # 17 |
如果又有一個新的問題,求x的1/m的n次方和y的和(測試用例:x=6,y=6,m=3,n=4),函式氏解決起來會更加酸爽。
1 2 3 4 5 6 7 8 9 |
def sum(x, y, f, g): return f(x) + g(y) def generate_func2(m, n): def func(x): return (x/m)**n return func sum(6, 6, generate_func2(3, 4), generate_func(1)) # 22 |
可以看到,函式氏最大的魅力在於,函式和資料都可以作為輸入,優勢很明顯,除了餵給你資料,還可以控制你如何吃資料,一切盡在掌握。
3.1 高階函式(Higher-order Function)
generate_func
著實幫了大忙,他可以動態生成函式,而無需人工蠻力定義。除了省時省力,最大的好處是生成的函式有無限個,這是人工所不能及的。
高階函式就是generate_func
這樣的函式,要麼輸入中至少有一個函式,要麼輸出一個函式,至少滿足兩個條件中的一個。何謂高階?高低是需要比較的,人比小兔子要高階,大學數學比初中數學要高階,我心裡的她要比其他人高階,這就是高階。高階函式比普通意義的函式高階。
為什麼高階函式更牛逼?有一個詞叫“泛化”,是從具體到抽象,抽象可以讓我們站在更高的位置看待這芸芸眾生,然後悟出什麼人生道理。什麼模組化、物件導向、設計模式blabla,我們不都是在追求抽象,追求“泛”嗎?從一個函式生成無數個函式,這不就是四兩撥千斤,一生二二生三嘛!
所以高階函式實至名歸。既然高階函式這麼“泛”,為啥不叫“泛函式”?
高階函式在數學中也叫做運算元(運算子)或泛函。
3.2 閉包(Closure)
當我們使用高階函式來生成函式的時候,可以使用以下兩種方法。方法一是將要生成的函式f
寫在高階函式gen_f
內部;方法二是將f
寫在外部。通常我們使用第一種,這就和定義區域性變數差不多,在哪用就在哪定義,肥水不流外人田,減少對外界的汙染。
1 2 3 4 5 6 7 8 9 10 11 |
# method 1 def gen_f(): def f(): pass return f # method 2 def f(): pass def gen_f(): return f |
一旦我們承認並習慣使用第一種方法的時候,就可能會寫出以下風格的程式碼。
1 2 3 4 5 6 7 8 9 10 11 |
def gen_f(): array = [] def f(): array.append(1) return array return f func = gen_f() func() # [1] func() # [1, 1] func() # [1, 1, 1] |
預料之外,情理之中,f
就是閉包函式。func
作為生成出來的函式,每次呼叫時都會往array
裡放一個數字1,而array
是在外部的gen_f
中定義的。這時就需要作出選擇,是修改外部的array
還是丟擲一個找不到array
的錯誤。支援閉包特性的程式語言選擇的是前者。
閉包函式和其引用的變數將一同存在,所以,有另一種說法認為閉包是由函式和與其相關的引用環境組合而成的實體。
3.3 柯里化(Currying)
如果我們習慣了生成函式帶來的快感,也很有可能寫出以下程式碼。方法一和方法二都實現了同樣的功能,不同之處在於方法一每次只傳一個引數,而方法二一次性把所有引數傳入進去。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# method 1 def f(x): def g(y): def h(z): return x + y + z return h return g f(1)(2)(3) # 6 # method 2 def f(x, y, z): return x + y + z f(1, 2, 3) # 6 |
儘管方法一看起來有點囉嗦,但這也意味函式氏在我們心中的地位大大提升。有點“後宮佳麗三千,卻偏偏寵她一人”的感覺啊哈。
從方法二到方法一的變換稱為柯里化,即把接受多個引數的函式變成每次只接受一個引數的函式,並返回接受餘下的引數的函式。有點拗口,就是通過多次呼叫函式來代替一次傳入多個引數。
柯里化的優勢在於可以將抽象的函式具體化,比如列印日誌。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def print_msg(label, msg): print '[%s] %s' % (label, msg) # no currying print_msg('error', 'network failed') print_msg('info', 'init ok') # use currying print_err_msg = curry(print_msg)('error') print_info_msg = curry(print_msg)('info') print_err_msg('network failed') print_info_msg('init ok') |
其中,函式curry
表示將輸入的函式柯里化。
3.4 偏函式(Partial Function)
壞訊息是Python並沒有提供curry
函式,好訊息是有一些第三方的可以實現該效果,比如https://github.com/kachayev/fn.py
,這個庫可以給Python插上函數語言程式設計的翅膀。
Python的內建functools
模組提供了類似curry
的功能,名曰偏函式。
1 2 3 4 5 6 7 8 9 10 11 |
from functools import partial def print_msg(label, msg): print '[%s] %s' % (label, msg) # use partial print_err_msg = partial(print_msg, 'error') print_info_msg = partial(print_msg, 'info') print_err_msg('network failed') print_info_msg('init ok') |
柯里化和偏函式類似但不同,柯里化是將多引數函式轉變為一系列單引數函式的鏈式呼叫,而偏函式是事先固定好一部分引數後面就無需重複傳入了。兩者都可以實現函式的具體化,固定函式的一部分引數來達到特定的應用。
3.5 匿名函式
使用函式原來可以如此之爽,恩,函式有意思。但是函式的定義著實是個麻煩,我們曾經寫過以下程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def sum(x, y, f, g): return f(x) + g(y) def f1(x): return x def f2(x): return x**2 def f3(x): return x**3 def f4(x): return x**4 sum(1, 2, f1, f1) # 3 sum(1, 2, f2, f2) # 5 sum(1, 2, f3, f4) # 17 |
煩。此時,匿名函式的威力便可發揮出來了,如下。lambda可以使我們達到快速定義並使用函式的效果,綠色無汙染,乾淨利索,棒!
1 2 3 4 5 6 |
def sum(x, y, f, g): return f(x) + g(y) sum(1, 2, lambda x:x, lambda x:x) # 3 sum(1, 2, lambda x:x**2, lambda x:x**2) # 5 sum(1, 2, lambda x:x**3, lambda x:x**4) # 17 |
3.6 map、reduce、filter
map、reduce、filter是Python內建的高階函式,通過傳入函式可以實現某些特定的功能,通過使用這些函式可以讓程式碼更加簡潔,逼格更上一層樓。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
array = [1,2,3] ### [1,2,3]變換為[1*1,2*2,3*3] # bad result = [] for i in array: result.append(i*i) # good map(lambda x:x*x, array) # [1,4,9] ### 求[1,2,3]中元素的和 # bad result = 0 for i in array: result += i # good reduce(lambda x,y:x+y, array) # 6 ### 求[1,2,3]中的奇數 # bad result = [] for i in array: if i % 2: result.append(i) # good filter(lambda x:x%2, array) # [1,3] |
參考
- http://baike.baidu.com/subview/11814478/19900888.htm
- https://zh.wikipedia.org/wiki/高階函式
- http://yixue.h.baike.com/article-86467.html
- https://zh.wikipedia.org/wiki/閉包_(電腦科學)
- https://www.zhihu.com/question/30097211/answer/46785556
- https://mtomassoli.wordpress.com/2012/03/18/currying-in-python/
- https://zh.wikipedia.org/zh-cn/柯里化
- https://segmentfault.com/a/1190000002376117
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式