Python中的返回函數與閉包

返回函數,顧名思義,就是高階函數能夠把函數做爲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()結果應該是149,但實際結果是:

>>> 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
相關文章
相關標籤/搜索