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())