閉包是指延伸了作用域的函數(shù),其中包含函數(shù)定義體中引用、但是不在定義體中定義的非全局變量。
閉包(closure)是函數(shù)式編程的重要的語(yǔ)法結(jié)構(gòu)。閉包也是一種組織代碼的結(jié)構(gòu),它同樣提高了代碼的可重復(fù)使用性。
當(dāng)一個(gè)內(nèi)嵌函數(shù)引用其外部作用域的變量,我們就會(huì)得到一個(gè)閉包. 總結(jié)一下,創(chuàng)建一個(gè)閉包必須滿足以下幾點(diǎn):
- 必須有一個(gè)內(nèi)嵌函數(shù)
- 內(nèi)嵌函數(shù)必須引用外部函數(shù)中的變量
- 外部函數(shù)的返回值必須是內(nèi)嵌函數(shù)
閉包是一種函數(shù),它會(huì)保留定義函數(shù)時(shí)存在的自由變量的綁定,這樣調(diào)用函數(shù)時(shí)雖然定義作用域不可用了,但仍能使用那些綁定。
閉包的概念難以掌握,下面通過(guò)示例進(jìn)行理解。
假設(shè)我們要實(shí)現(xiàn)一個(gè)計(jì)算移動(dòng)平均功能的代碼,如何實(shí)現(xiàn)呢?
初學(xué)者可能會(huì)用類來(lái)實(shí)現(xiàn),如示例1:
# 示例1
class Averager(object):
def __init__(self):
self.series = []
def __call__(self, new_value):
self.series.append(new_value)
total = sum(self.series)
return total/len(self.series)
Average的實(shí)例是可調(diào)用對(duì)象:
>>> avg = Averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
下面使用函數(shù)式實(shí)現(xiàn),如示例2:
# 示例2
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
調(diào)用make_averager時(shí),返回一個(gè)averager函數(shù)對(duì)象。每次調(diào)用averager時(shí),該對(duì)象會(huì)把參數(shù)添加到series中,然后計(jì)算當(dāng)前平均值,如下所示:
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
以上兩個(gè)示例有共通之處:調(diào)用Averager()或make_averager()得到一個(gè)可調(diào)用對(duì)象avg,該對(duì)象會(huì)更新歷史值,然后計(jì)算當(dāng)前均值。示例1中,avg是Averager的實(shí)例;實(shí)例2中是內(nèi)部函數(shù)averager。不管怎樣,我們都只需要調(diào)用avg(n),把n放入系列值series中,然后重新計(jì)算均值。
Averager()類的實(shí)例avg在哪里存儲(chǔ)歷史值很明顯,但是第二個(gè)示例中的avg函數(shù)在哪里尋找series呢?
注意,series是make_averager函數(shù)的局部變量,因?yàn)槟莻€(gè)函數(shù)的定義體中初始化了series = []??墒?,調(diào)用avg(10)時(shí),make_averager函數(shù)已經(jīng)返回了,而他的本地作用域也一去不復(fù)返了。
在averager函數(shù)中,series是自由變量,指未在本地作用域中綁定的變量,圖形化展示如下:

averager的閉包延伸到那個(gè)函數(shù)的作用域之外,包含對(duì)自由變量series的綁定
我們可以審查返回的averager對(duì)象,發(fā)現(xiàn)Python在__code__屬性(表示編譯后的函數(shù)定義體)中保存局部變量和自由變量的名稱,如下所示
# 審查make_averager創(chuàng)建的函數(shù)
>>> avg.__code__.co_varnames
('new_value', 'total')
>>> avg.__code__.co_freevars
('series',)
series綁定在返回的avg函數(shù)的__closure__屬性中。avg.__closure__中各個(gè)元素對(duì)應(yīng)于avg.__code__.co_freevars中的一個(gè)名稱。這些元素是cell對(duì)象,有個(gè)cell_content屬性,保存著真正的值。這些屬性的值如示例所示:
>>> avg.__code__.co_freevars
('series',)
>>> avg.__closure__
(<cell at 0x108b89828: list object at 0x108ae96c8>,)
>>> avg.__closure__[0].cell_contents
[10,11,12]
綜上,閉包是一種函數(shù),它會(huì)保留定義函數(shù)時(shí)存在的自由變量的綁定,這樣調(diào)用函數(shù)時(shí)雖然定義作用域不可用了,但仍能使用那些綁定。
稍微有點(diǎn)編程經(jīng)驗(yàn)的人會(huì)看到,我們實(shí)現(xiàn)的計(jì)算移動(dòng)平均值得方法實(shí)際上有很大的改進(jìn)空間,在后面的介紹中會(huì)進(jìn)行改進(jìn)。
歡迎關(guān)注微信公眾號(hào):CodeWorks
問(wèn)題或建議,請(qǐng)公眾號(hào)留言,歡迎非抬杠式討論