簡(jiǎn)談Python中的閉包

閉包是指延伸了作用域的函數(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):

  1. 必須有一個(gè)內(nèi)嵌函數(shù)
  2. 內(nèi)嵌函數(shù)必須引用外部函數(shù)中的變量
  3. 外部函數(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)留言,歡迎非抬杠式討論

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容