返回函數,顧名思義,就是高階函數能夠把函數做爲return值返回。與閉包的關係是:閉包須要以返回函數的形式實現。python
一. 返回函數編程
好比咱們有一個求和函數:閉包
>>> def calc_sum(num_list): s = 0 for i in num_list: s += i return s >>> calc_sum([1,2,3,4]) 10
當咱們不須要馬上求和,而是後面根據須要再計算結果時,咱們能夠返回求和的函數,而不是直接返回計算結果。這就是返回函數。app
>>> def lazy_calc_sum(num_list): def calc_sum(): s = 0 for i in num_list: s += i return s return calc_sum >>> f_lazy = lazy_calc_sum([1,2,3,4]) >>> f_lazy <function lazy_calc_sum.<locals>.calc_sum at 0x0000003A8D92E9D8> >>> f_lazy() 10
很顯然,這樣能讓咱們根據需求,節省計算資源。函數式編程
二. 閉包函數
在上面的例子中,咱們在函數lazy_clac_sum
中又定義了函數calc_sum
,而且,內部函數calc_sum
能夠引用外部函數lazy_calc_sum
的參數和局部變量,當lazy_calc_sum
返回函數calc_sum
時,相關參數和變量都保存在返回的函數中,這種稱爲「閉包(Closure)」。spa
若是讓定義更加清晰一些: 若是在一個內部函數裏對在外部做用域(但不是在全局做用域)的變量進行引用,但不在全局做用域裏,則這個內部函數就是一個閉包。code
實際上,閉包的用處/優勢有兩條:對象
下面例子是,咱們建立了一個下載download函數,而後下載次數一直存儲在內存中。blog
>>> def download_enter(download_times): def download():
nonlocal download_times download_times += 1 print("This is the %s time download" % download_times) return download >>> >>> d = download_enter(0) >>> d() This is the 1 time download >>> d() This is the 2 time download >>> d() This is the 3 time download
下面的例子是閉包根據外部做用域的局部變量來獲得不一樣的結果,這有點像一種相似配置功能的做用,咱們能夠修改外部的變量,閉包根據這個變量展示出不一樣的功能。好比有時咱們須要對某些文件的特殊行進行分析,先要提取出這些特殊行。
def make_filter(keep): def the_filter(file_name): file = open(file_name) lines = file.readlines() file.close() filter_doc = [i for i in lines if keep in i] return filter_doc return the_filter
若是咱們須要取得文件"result.txt"中含有"pass"關鍵字的行,則能夠這樣使用例子程序
filter = make_filter("pass") filter_result = filter("result.txt")
以上兩種使用場景,用面向對象也是能夠很簡單的實現的,可是在用Python進行函數式編程時,閉包對數據的持久化以及按配置產生不一樣的功能,是頗有幫助的。
關於閉包的2個常見錯誤:
1. 嘗試在閉包中改變外部做用域的局部變量
def foo(): a = 1 def bar(): a = a + 1 return a return bar
>>> c = foo() >>> print c() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in bar UnboundLocalError: local variable 'a' referenced before assignment
這段程序的本意是要經過在每次調用閉包函數時都對變量a進行遞增的操做。但在實際使用時,a = a + 1的a會被python解釋器認爲是bar()函數的局部變量,從而引發「referenced before assignment」的錯誤。
解決方法有兩個:
方法一:將a設置爲一個容器,好比列表List (不推薦)
方法二:將a聲明爲nonlocal變量(僅在Python3支持),這樣聲明事後,就不會被認爲是bar()函數的局部變量,而是會到上一層函數環境中尋找這個變量。
下面是例子:
>>> def foo(): a = 1 b = [1] def bar(): nonlocal a a = a + 1 b[0] = b[0] + 1 return a,b[0] return bar >>> c = foo() >>> print(c()) (2, 2) >>> print(c()) (3, 3) >>> print(c()) (4, 4)
2. 誤覺得返回的函數就已執行,對執行結果誤判
直接舉例子說明:
def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count()
在上面的例子中,每次循環,都建立了一個新的函數,而後,把建立的3個函數都返回了。
你可能認爲調用f1()
,f2()
和f3()
結果應該是1
,4
,9
,但實際結果是:
>>> f1() 9 >>> f2() 9 >>> f3() 9
所有都是9
!緣由就在於返回的函數引用了變量i
,但它並不是馬上執行。等到3個函數都返回時,它們所引用的變量i
已經變成了3
,所以最終結果爲9
。
返回閉包時牢記一點:返回函數不要引用任何循環變量,或者後續會發生變化的變量。
若是必定要引用循環變量怎麼辦?方法是再建立一個函數,用該函數的參數綁定循環變量當前的值,不管該循環變量後續如何更改,已綁定到函數參數的值不變:
def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)馬上被執行,所以i的當前值被傳入f() return fs
結果是:
>>> f1, f2, f3 = count() >>> f1() 1 >>> f2() 4 >>> f3() 9