一、什麼是閉包
python
在介紹閉包概念前,咱們先來看一段簡短的代碼數組
def sum_calc(*args): def wrapper(): sum = 0 for n in args: sum += n; return sum return wrapper
很顯然,這段代碼定義了一個名爲sum_calc的函數,但和定義的普通函數不一樣的是這個函數體的內部又定義了一個名爲wrapper的函數,而且sum_calc函數的返回值是內部定義wrapper函數。閉包
如今咱們開始來調用sum_calc函數,看看會出現哪些有趣的事情app
>>> f = sum_calc(1, 2, 3, 4, 5) >>> f <function sum_calc.<locals>.wrapper at 0x0000025693AC2D30>
>>> f() 15
從運行結果來看,當咱們調用sum_calc時,返回的並非求和結果,而是內部定義的求和函數。函數
繼續調用sum_calc返回函數時,才真正計算出求和的結果。spa
當咱們繼續調用一次sum_calc,傳入相同參數code
>>> f1 = sum_calc(1, 2, 3, 4, 5) >>> f2 = sum_calc(1, 2, 3, 4, 5) >>> f1 == f2 False
>>> f1()
15
>>> f2()
15
每次調用sum_calc,即便傳入相同的參數,兩次返回的對象不一樣,且f1()和f2()的結果互不影響。對象
在這個例子中,咱們在sum_calc函數中定義的wrapper函數,wrapper函數能夠引用外部函數sum_calc的參數和局部變量,當sum_calc返回函數wrapper時,相關參數保存在返回的函數中,能夠被返回的函數繼續使用,咱們把這種狀況稱爲「閉包」。blog
參考維基百科,對「閉包」進行更嚴謹的解釋:io
在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即便已經離開了創造它的環境也不例外。因此,有另外一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時能夠有多個實例,不一樣的引用環境和相同的函數組合能夠產生不一樣的實例。
二、閉包陷阱
def my_fun(): fs = [] for i in range(0, 3): def f(): return i*i fs.append(f) return fs
f1, f2, f3 = count()
在上面的例子中,每次循環,都建立了一個新的函數,而後,把建立的3個函數都放在列表中返回。
咱們可能認爲,f1()、f2()、f3()結果是0、一、4,但實際結果是4
>>> f1() 4 >>> f2() 4 >>> f3() 4
這個例子能夠說是典型的錯誤使用閉包的案例,my_fun返回的並非一個閉包函數,而是一個包含三個閉包函數的一個列表。
在返回閉包列表fs以前for循環的變量的值已經發生改變了,循環內閉包函數僅聲明瞭本身計算方式,並不會當即使用當前i的值進行計算。只有在真正調用閉包函數時,纔會真正的執行閉包函數內的計算,而此時存放的i的值已是2,因此f1()、f2()、f3()的結果是4而不是我在以前期待的0、一、4。
通過上面的分析,咱們得出下面一個重要的經驗:返回閉包中不要引用任何循環變量,或者後續會發生變化的變量。
正確寫法
def my_fun(*args): L = [] for i in range(3): def wrapper(_i = i): return _i * _i L.append(wrapper) return L
三、閉包實現機制
閉包比普通的函數多了一個 __closure__ 屬性,該屬性記錄着自由變量內容。當閉包被調用時,系統就會根據該地址找到對應的自由變量,完成總體的函數調用
還以 sum_calc() 爲例,當其被調用時,能夠經過 __closure__ 屬性獲取自由變量(也就是程序中的 args參數)內容,例如:
def sum_calc(*args): def wrapper(): sum = 0 for n in args: sum += n; return sum return wrapper
輸出結果:
>>> f1 = sum_calc(1, 2, 3, 4, 5) >>> f1.__closure__ (<cell at 0x0000025693A5E4C0: tuple object at 0x0000025693AE5400>,)
>>> f1.__closure__[0].cell_contents
(1, 2, 3, 4, 5)
能夠看到,顯示的內容是一個tuple類型,這就是f1中自由變量args的值。還能夠看到,__closure__ 屬性的類型是一個元組,這代表閉包能夠支持多個自由變量的形式。