1. 程式人生 > >Python3之閉包

Python3之閉包

何為閉包
維基百科中關於閉包的概念:
在一些語言中,在函式中可以(巢狀)定義另一個函式時,如果內部的函式引用了外部的函式的變數,則可能產生閉包。閉包可以用來在一個函式與一組“私有”變數之間建立關聯關係。在給定函式被多次呼叫的過程中,這些私有變數能夠保持其永續性。

 

閉包條件

根據這句話,其實我們自己就可以總結出在python語言中形成閉包的三個條件,缺一不可:
1)必須有一個內嵌函式(函式裡定義的函式)——這對應函式之間的巢狀
2)內嵌函式必須引用一個定義在閉合範圍內(外部函式裡)的變數——內部函式引用外部變數
3)外部函式必須返回內嵌函式——必須返回那個內部函式

前兩個條件我們比較好理解,那什麼會有第三條規定呢?其實閉包一詞指的就是上文中提到的那個“內部的函式”,我們下面就會發現,只有那個內部函式才有所謂的__closure__屬性。

我們首先來創造一個閉包:

def funx():
    x=5
    def funy():
        nonlocal x
        x+=1
        return x
    return funy

我們根據上面的三準則創造了一個函式,其中的funy就是所謂的閉包,而funy內部所引用過的x就是所謂的閉包變數。

測試:

>>> a=funx()
>>> a()
6
>>> a()
7
>>> a()
8
>>> a()
9
>>> x
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    x
NameError: name 'x' is not defined
>>>

我們會發現,funx中的x變數原本僅僅是funx的一個區域性變數。但是形成了閉包之後,它的行為就好像是一個全域性變數一樣。但是最後的錯誤說明x並不是一個全域性變數。其實這就是閉包的一個十分淺顯的作用,形成閉包之後,閉包變數能夠隨著閉包函式的呼叫而實時更新,就好像是一個全域性變數那樣。(注意我們上面的a=funx(),a實際上應該是funy,所以a稱為閉包)


進一步探究
我們能否找出點證據證明我們對於閉包的猜想呢?很簡單,我們可以嘗試下面的操作:

>>> a.__closure__
(<cell at 0x0000002F346FB408: int object at 0x00000000667D02D0>,)
>>> type(a.__closure__)
<class 'tuple'>
>>> type(a.__closure__[0])
<class 'cell'>
>>> a.__closure__[0].cell_contents
9
>>> a()
10
>>> a.__closure__[0].cell_contents
10
>>> def test():pass

>>> test.__closure__==None
True
>>>

這樣我們就明白了,形成閉包之後,閉包函式會獲得一個非空的__closure__屬性(對比我們最後的函式test,test是一個不具備閉包的函式,它的__closure__屬性是None),這個屬性是一個元組。元組裡面的物件為cell物件,而訪問cell物件的cell_contents屬性則可以得到閉包變數的當前值(即上一次呼叫之後的值)。而隨著閉包的繼續呼叫,變數會再次更新。所以可見,一旦形成閉包之後,python確實會將__closure__和閉包函式繫結作為儲存閉包變數的場所。

 

 

 

 

 

def outer():
    count = 0
    def inner():
        nonlocal count
        count +=1
        return count
    return inner


f=outer()
print(f())
print(f())

# 輸出
1
2
>>>


#nonlocal count出錯,count不能為全域性變數
count = 0
def outer():
    def inner():
        nonlocal count
        count +=1
        return count
    return inner


f=outer()
print(f())
print(f())