什么是閉包
我們可以將閉包理解為一個函數(shù)引用了它所在詞法作用域的變量之后形成的一種數(shù)據(jù)結(jié)構(gòu).
詞法作用域顶霞,就是定義一個函數(shù)時(shí)因惭,對它可見的作用域,又叫做環(huán)境哥牍,是由多個作用域組成的棧.舉個例子:
def user_required(user):
def decorator(fn):
def wrapper(*args, **kwargs):
if current_user == user:
return fn(*args, **kwargs)
else:
return no_permission_response
return wrapper
return decorator
這個函數(shù)是一個裝飾器非迹,對函數(shù)fn進(jìn)行裝飾后环鲤,就只有特定類型的user可以調(diào)用fn,否則返回no_pemission_response.
這里憎兽,wrapper的詞法作用域就是由decorator形成的部分作用域冷离,user_required的部分作用域以及外面的全局作用域組成,總之就是wrapper能訪問的部分唇兑,也可以稱之為wrapper所處的環(huán)境.
當(dāng)一個函數(shù)引用了它所處環(huán)境的變量的時(shí)候酒朵,比如這里wrapper引用了環(huán)境里的fn和user,這個函數(shù)就形成了一個閉包扎附,這個時(shí)候函數(shù)就不再是一個與外界環(huán)境無關(guān)的函數(shù)蔫耽,而是會因?yàn)橥饨绛h(huán)境改變而改變的函數(shù),引用的環(huán)境變量稱為自由變量.
閉包與作用域
注意形成閉包的時(shí)候閉包保存的是當(dāng)前的環(huán)境留夜,而不是自由變量在此刻的值匙铡,因此不管自由變量是值語義還是引用語義,閉包在執(zhí)行的時(shí)候都會從保存的環(huán)境中獲取它們當(dāng)前的值.舉個經(jīng)常被提起的例子:
def gen_fns():
return [lambda: x for x in xrange(5)]
print [fn() for fn in gen_fns()]
結(jié)果為[4, 4, 4, 4, 4]
出現(xiàn)這種情況的原因就是Python的列表生成式只會產(chǎn)生一個作用域碍粥,產(chǎn)生的5個閉包保存了同樣的環(huán)境鳖眼,所有的x都是同一個x,gen_fns返回時(shí)x的值是迭代結(jié)束時(shí)的值4,閉包被調(diào)用時(shí)去查找環(huán)境中的x嚼摩,自然就得到了4.
要讓結(jié)果為[0, 1, 2, 3, 4]
钦讳,我們可以讓閉包形成時(shí)處在不同的環(huán)境中,注意Python只有類定義和函數(shù)定義(包括lambda)能形成作用域枕面,因此我們可以這樣處理:
def gen_fns():
return [(lambda x: lambda: x)(x) for x in xrange(5)]
print [fn() for fn in gen_fns()]
這里lambda x: lambda: x
中外層的lambda形成了一個作用域愿卒,并且該作用域里的x不是自由變量,而是被外層lambda捕獲而成為僅屬于該作用域的變量潮秘,因此這里產(chǎn)生的5個閉包都保存了一樣的環(huán)境琼开,不一樣的x,而每個x的值都為形成閉包時(shí)的值.
注意閉包和值語義引用語義沒有關(guān)系枕荞,事實(shí)上柜候,僅僅是對變量本身進(jìn)行操作(比如賦值和加減法),值語義和引用語義根本沒有差別搞动,只有對變量進(jìn)行間接操作(其實(shí)也只有引用語義才存在間接操作)(比如調(diào)用變量的set_xxx方法)時(shí),才有可能區(qū)別值語義和引用語義.
閉包的作用
通常一個函數(shù)的返回結(jié)果僅與輸入有關(guān)渣刷,也就是說函數(shù)本身是不保存任何狀態(tài)的鹦肿,而閉包則是保存了環(huán)境(詞法作用域)的函數(shù),其輸出不僅與輸入有關(guān)辅柴,也會受環(huán)境的影響狮惜,不同的環(huán)境產(chǎn)生的閉包是不一樣的,相同的環(huán)境不同的狀態(tài)閉包調(diào)用的結(jié)果也不一樣碌识,通過閉包,我們可以獲取和改變閉包保存的環(huán)境虱而,這樣閉包就成為了環(huán)境之間交互的通道.