閉包的作用
一句話大莫,閉包的作用:將方法存于變量蛉腌。
至于閉包的原因或者目的,或者說只厘,為什么將方法存于變量烙丛,稍后再說。
閉包的條件
為了盡量避免用一大段話描述一個(gè)概念羔味,我們理性一點(diǎn)地把閉包的條件劃分成3個(gè):
- 外函數(shù)中定義了一個(gè)內(nèi)函數(shù)
- 內(nèi)函數(shù)用了外函數(shù)的變量
- 外函數(shù)返回了內(nèi)函數(shù)的引用河咽,or,外函數(shù)中直接調(diào)用了內(nèi)函數(shù)
P.S.
- 其中外函數(shù)和內(nèi)函數(shù)是指嵌套函數(shù)中外部函數(shù)和內(nèi)部函數(shù)
- 也正是因?yàn)樾枰短缀瘮?shù)赋元,因此不支持的嵌套函數(shù)的語言也自然不支持此類閉包
- 條件3中分成了兩類忘蟹,更多的情況下是前一類,而后一類(外函數(shù)直接調(diào)用了內(nèi)函數(shù))的使用更多的是為了保證代碼的簡(jiǎn)潔搁凸,而如此地保持簡(jiǎn)潔并不一定用閉包媚值。
閉包的例子
“Talk is cheap, show me your code.”
我始終覺得,在編程中护糖,過多的人類語言會(huì)產(chǎn)生太多的歧義褥芒,甚至還可能會(huì)因?yàn)樗f事物過于抽象而導(dǎo)致聽眾無法將概念理解。
而解決這個(gè)問題最好的方法就是看代碼嫡良,編程語言相較于人類語言的優(yōu)點(diǎn)之一是大幅地降低了語言的歧義锰扶。同時(shí),通過多個(gè)代碼實(shí)例皆刺,人腦會(huì)自然而然地將多實(shí)例中的共同點(diǎn)提取出來少辣,進(jìn)而理解抽象的概念凌摄。
結(jié)合閉包的三個(gè)條件羡蛾,我們來看看閉包的例子:
def outer(b):
def inner(a): # 條件1
print(a + b) # 條件2
return inner # 條件3
# 調(diào)用
o = outer(1)
o(2)
Python的代碼還是挺簡(jiǎn)單的。
一般情況下锨亏,在函數(shù)結(jié)束后痴怨,函數(shù)中變量等就應(yīng)該被銷毀忙干,偏偏這個(gè)閉包就是個(gè)特例 —— o和o2中的1和20都保留著。
o和o2看起來就有那么一絲熟悉的感覺浪藻,它們兩個(gè)就像是兩個(gè)對(duì)象 —— 這兩個(gè)“對(duì)象”都是從同一個(gè)“類”出來的捐迫,而兩個(gè)“對(duì)象實(shí)例”的區(qū)別是有一個(gè)加數(shù)不一樣,分別是1和20(當(dāng)然爱葵,這兩個(gè)變量的引用地址也不同)施戴。
現(xiàn)在,我們把代碼例子中的第三個(gè)條件變一下萌丈,即將“外函數(shù)返回了內(nèi)函數(shù)的引用”變成“外函數(shù)中直接調(diào)用了內(nèi)函數(shù)”:
def outer(a, b):
def inner(a): # 條件1
print(a + b) # 條件2
inner(b) # 條件3
# 調(diào)用
o = outer(100赞哗,1)
此時(shí),整個(gè)閉包函數(shù)調(diào)用起來就和一個(gè)普通函數(shù)一樣辆雾,傳入兩個(gè)參數(shù)肪笋,該print的也如期而至。
只能說度迂,在outer函數(shù)內(nèi)的邏輯過于復(fù)雜的時(shí)候藤乙,inner能把復(fù)雜的代碼“模塊化”,再調(diào)用惭墓,能增加簡(jiǎn)潔性坛梁。這種情況下,一般是inner函數(shù)只被調(diào)用一次诅妹,而且只在這里調(diào)用罚勾,放在這里也好管理一些。
接下來吭狡,我們也鑒賞一下別的語言類似的閉包:
func outer(i int) func() int {
return func() int { // 條件1(匿名)+ 條件3
i++ // 條件2
return i
}
}
// 調(diào)用
o := outer(1)
o()
這個(gè)Golang的例子閉包和Python的例子較大的距別是這里還把內(nèi)函數(shù)換成了匿名的尖殃,看起來會(huì)爽點(diǎn)。而以下的php的就和Python的差不多了划煮。
function outer($str1) {
$outerStr = $str1;
$inner = function($str2) { // 條件1
echo $str2 . $outerStr; // 條件2
};
return $inner; // 條件3
}
// 調(diào)用
$o = outer("hahaha");
$o("emmm");
閉包的原因
看過了上面的例子后送丰,新手對(duì)閉包的概念也應(yīng)該有了一定的理解,甚至有點(diǎn)想法了弛秋。
回到最開始的問題器躏,即閉包的原因。
如果說蟹略,“將方法存于變量”是閉包的目的登失,那么接下來的問題顯而易見:為什么要將方法存于變量?直接調(diào)用方法(函數(shù))不好嗎挖炬?
閉包保存了函數(shù)的狀態(tài)信息
再舉開頭的例子:
def outer(b):
def inner(a):
print(a + b)
return inner
o = outer(1)
o(2) # 3
o(100) # 101
o2 = outer(20)
o2(100) # 120
o這個(gè)變量對(duì)應(yīng)的閉包保存了b=1
這個(gè)信息揽浙,之后無論是調(diào)用o(2)
還是o(100)
,b=1
這個(gè)信息依然會(huì)存在并和后來的參數(shù)一起參與運(yùn)算。同理馅巷,o2這個(gè)變量對(duì)應(yīng)的閉包保存了b=20
這個(gè)信息膛虫。
由于退出了函數(shù)后,函數(shù)并沒有并銷毀钓猬,這個(gè)閉包的信息也沒銷毀稍刀,因此后續(xù)可以利用這些信息。
語法糖
為了代碼的簡(jiǎn)潔性和易理解性敞曹,我們經(jīng)常會(huì)使用甚至創(chuàng)造一些語法糖账月。
而在Python中,有一個(gè)十分好看的例子就是裝飾器澳迫,舉個(gè)已經(jīng)被用爛了的例子捶障,面向切面的登錄實(shí)現(xiàn):
# 先實(shí)現(xiàn)一個(gè)類似于裝飾器的函數(shù)
def decorator(func):
def inner():
print 'before function'
func() # function
print 'after function'
return inner
# 實(shí)現(xiàn)一個(gè)假裝在登錄的登錄函數(shù)
def login():
print 'login function complete.'
# 將登錄函數(shù)“套上”裝飾器
login = decorator(login)
login()
整個(gè)過程下來與AOP類似,而場(chǎng)景也很常用纲刀,如統(tǒng)計(jì)函數(shù)的運(yùn)行時(shí)常项炼、加入日志、統(tǒng)一的過濾處理等等示绊。
更有甚者锭部,在特定的場(chǎng)景下使用閉包創(chuàng)造語法糖,以簡(jiǎn)化代碼面褐,參考這個(gè)例子拌禾,該例子可以替代switch(不過這里這么簡(jiǎn)單的加減運(yùn)算這樣寫就很智障了,要有一定的復(fù)雜度就能顯得有優(yōu)越性):
def operator(o):
def plus(x, y):
print(x + y)
def minus(x, y):
print(x - y)
if o == '+':
return plus
if o == '-':
return minus
def f(x, o, y):
operator(o)(x, y)
函數(shù)式編程
鼎鼎大名的Lambda展哭,這個(gè)可以參考下這個(gè)鏈接湃窍。
總結(jié)
閉包能將方法存于變量,且實(shí)現(xiàn)一些美妙的東西匪傍。
它就像是調(diào)味劑您市,并非不可或缺,但是能錦上添花役衡。
先這樣吧
若有錯(cuò)誤之處請(qǐng)指出茵休,更多地關(guān)注煎魚。