(1)變數的域
要了解閉包需要先了解變數的域,也就是變數在哪一段“上下文”是有效的(類似區域性變數和全域性變數的區別),舉一個很簡單的例子。(例子不重要,就是涉及閉包就要時刻關注這個域)
1 def test(): 2 msg2 = 'test中的' 3 print('====',msg1) # ==== 非test中的 4 msg1 = '非test中的' 5 test() 6 print(msg1) # 非test中的 7 print(msg2) # 報錯
(2)什麼是閉包
維基百科定義:閉包(Closure)或閉包函式(function Closure),是引用了自由變數的函式。這個被引用的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。所以有另一種說法認為閉包是由函式和與其相關的引用環境組合而成的實體。
定義可以反覆體會,先看一個比較有意思的例子:
1 def test(): 2 msg = '我是test中的' 3 def test2(): 4 print(msg) 5 return test2 6 g = test() 7 g() # 我是test中的
這段程式碼執行到第七行的時候,輸出了msg的內容。
我們在對比【(1)變數的域】中的程式碼例子,那個例子中,同樣是第七行的時候是報錯,因為很好理解,上一個例子msg2已經脫離了test()函式,也就是區域性變數只能在內部使用,不能夠全域性使用。
然後回到這個例子,這就是閉包存在的意義,我們可以在外部訪問區域性變數。閉包就是:函式+上下文。注意到我這個例子第五行返回的是test2,是一個函式物件(in Python everything is an object )。這裡的 g 就是閉包。而我所說的上下文:就是各種變數環境。所以第七行的return,返回的function不是普通function,是帶著上下文環境一起的(也就是test2() 函式中帶有msg,而msg其實實在test()中定義的,但是也會被test2()帶在身邊)。不單單隻返回第三第四行兩行簡單的內容。
(3)閉包疑點
我們再看幾個 stockoverflow 和 官網上的幾個關於閉包的例子與疑點:
1.例子一
1 adders=[0,1,2,3] 2 3 for i in [0,1,2,3]: 4 adders[i]=lambda a: i+a 5 6 print(adders[1](3)) # 6
這個裡之中adders列表儲存了匿名函式,adders[1](3) 就是訪問adders[1] 中的匿名函式,引數是3,也就是lambda a:i+a(3傳遞給a,i 是for...in 迴圈給的,計算結果是 i + a)。
奇怪的是結果adders[1](3) = 6.我們可能會想應該是4阿,1 + 3 = 4。
我的理解是這樣的:因為我們需要注意這裡的 i 到底是屬於誰的,i 是在 for...in 中定義的,一個迴圈至始至終只有一個 i ,也就是 i 的引用是不變的,變得是 i 得值,所以lambda中的 i ,adders[0],adders[1]....中的 i 都是指向同一個 i ,而最後 i 是三。adders[0,1,2,3] 中的 lambda 匿名函式的引數 i 全都是同一個,這個 i 因為迴圈最終值是3. 所以3+3=6.(也就是 i 是什麼時候定義的?這個問題考慮好,是在呼叫lambda之前,也就是for迴圈開始的時候定義好的)
改進方案:
1 adders=[0,1,2,3] 2 3 for i in [0,1,2,3]: 4 adders[i]=lambda a, b = i: b+a 5 6 print(adders[0](3)) # 3 7 print(adders[1](3)) # 4 8 print(adders[2](3)) # 5 9 print(adders[3](3)) # 6
那我們就在lambda之中定義一個b,這個b是記錄i,但是adders[ ....] 陣列中的 b 是各不相同的引用哦。
2.例子二
1 squares = [] 2 for x in range(5): 3 squares.append(lambda: x**2) 4 5 print(squares[2]()) # 16 6 print(squares[3]()) # 16
情況一模一樣。最後全都算 4 *4 = 16。改進如下:
1 squares = [] 2 for x in range(5): 3 squares.append(lambda b = x: b**2) 4 5 print(squares[2]()) # 4 6 print(squares[3]()) # 9