這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
閉包的作用
一句話,閉包的作用:將方法存於變數。
至於閉包的原因或者目的,或者說,為什麼將方法存於變數,稍後再說。
閉包的條件
為了盡量避免用一大段話描述一個概念,我們理性一點地把閉包的條件劃分成3個:
- 外函數中定義了一個內函數
- 內函數用了外函數的變數
- 外函數返回了內函數的引用,or,外函數中直接調用了內函數
P.S.
- 其中外函數和內函數是指嵌套函數中外部函數和內建函式
- 也正是因為需要嵌套函數,因此不支援的嵌套函數的語言也自然不支援此類閉包
- 條件3中分成了兩類,更多的情況下是前一類,而後一類(外函數直接調用了內函數)的使用更多的是為了保證代碼的簡潔,而如此地保持簡潔並不一定用閉包。
閉包的例子
“Talk is cheap, show me your code.”
我始終覺得,在編程中,過多的人類語言會產生太多的歧義,甚至還可能會因為所說事物過於抽象而導致聽眾無法將概念理解。
而解決這個問題最好的方法就是看代碼,程式設計語言相較於人類語言的優點之一是大幅地降低了語言的歧義。同時,通過多個代碼執行個體,人腦會自然而然地將多執行個體中的共同點提取出來,進而理解抽象的概念。
結合閉包的三個條件,我們來看看閉包的例子:
def outer(b): def inner(a): # 條件1 print(a + b) # 條件2 return inner # 條件3 # 調用o = outer(1)o(2)
Python的代碼還是挺簡單的。
一般情況下,在函數結束後,函數中變數等就應該被銷毀,偏偏這個閉包就是個特例 —— o和o2中的1和20都保留著。
o和o2看起來就有那麼一絲熟悉的感覺,它們兩個就像是兩個對象 —— 這兩個“對象”都是從同一個“類”出來的,而兩個“對象執行個體”的區別是有一個加數不一樣,分別是1和20(當然,這兩個變數的引用地址也不同)。
現在,我們把代碼例子中的第三個條件變一下,即將“外函數返回了內函數的引用”變成“外函數中直接調用了內函數”:
def outer(a, b): def inner(a): # 條件1 print(a + b) # 條件2 inner(b) # 條件3 # 調用o = outer(100,1)
此時,整個閉包函數調用起來就和一個普通函數一樣,傳入兩個參數,該print的也如期而至。
只能說,在outer函數內的邏輯過於複雜的時候,inner能把複雜的代碼“模組化”,再調用,能增加簡潔性。這種情況下,一般是inner函數只被調用一次,而且只在這裡調用,放在這裡也好管理一些。
接下來,我們也鑒賞一下別的語言類似的閉包:
func outer(i int) func() int { return func() int { // 條件1(匿名)+ 條件3 i++ // 條件2 return i }}// 調用o := outer(1)o()
這個Golang的例子閉包和Python的例子較大的距別是這裡還把內函數換成了匿名的,看起來會爽點。而以下的php的就和Python的差不多了。
function outer($str1) { $outerStr = $str1; $inner = function($str2) { // 條件1 echo $str2 . $outerStr; // 條件2 }; return $inner; // 條件3}// 調用$o = outer("hahaha");$o("emmm");
閉包的原因
看過了上面的例子後,新手對閉包的概念也應該有了一定的理解,甚至有點想法了。
回到最開始的問題,即閉包的原因。
如果說,“將方法存於變數”是閉包的目的,那麼接下來的問題顯而易見:為什麼要將方法存於變數?直接調用方法(函數)不好嗎?
閉包儲存了函數的狀態資訊
再舉開頭的例子:
def outer(b): def inner(a): print(a + b) return inner o = outer(1)o(2) # 3o(100) # 101o2 = outer(20)o2(100) # 120
o這個變數對應的閉包儲存了b=1
這個資訊,之後無論是調用o(2)
還是o(100)
,b=1
這個資訊依然會存在並和後來的參數一起參與運算。同理,o2這個變數對應的閉包儲存了b=20
這個資訊。
由於退出了函數後,函數並沒有並銷毀,這個閉包的資訊也沒銷毀,因此後續可以利用這些資訊。
文法糖
為了代碼的簡潔性和易理解性,我們經常會使用甚至創造一些文法糖。
而在Python中,有一個十分好看的例子就是裝飾器,舉個已經被用爛了的例子,面向切面的登入實現:
# 先實現一個類似於裝飾器的函數def decorator(func): def inner(): print 'before function' func() # function print 'after function' return inner# 實現一個假裝在登入的登入函數def login(): print 'login function complete.'# 將登入函數“套上”裝飾器login = decorator(login) login()
整個過程下來與AOP類似,而情境也很常用,如統計函數的運行時常、加入日誌、統一的過濾處理等等。
更有甚者,在特定的情境下使用閉包創造文法糖,以簡化代碼,參考這個例子,該例子可以替代switch(不過這裡這麼簡單的加減運算這樣寫就很智障了,要有一定的複雜度就能顯得有優越性):
def operator(o): def plus(x, y): print(x + y) def minus(x, y): print(x - y) if o == '+': return plus if o == '-': return minusdef f(x, o, y): operator(o)(x, y)
函數式編程
鼎鼎大名的Lambda,這個可以參考下這個連結。
總結
閉包能將方法存於變數,且實現一些美妙的東西。
它就像是調味劑,並非不可或缺,但是能錦上添花。
先這樣吧
若有錯誤之處請指出,更多地關注煎魚。