Python 中的閉包

今天有同事說道閉包,查了下Python中的閉包,看到下面這邊文字,記錄備查:html

閉包這個概念在不少語言中都有涉及,本文主要談談python中的閉包。Python中使用閉包主要是在進行函數式開發時使用。python

一,定義
編程

python中的閉包從表現形式上定義(解釋)爲:若是在一個內部函數裏,對在外部做用域(但不是在全局做用域)的變量進行引用,那麼內部函數就被認爲是閉包(closure).這個定義是相對直白的,好理解的,不像其餘定義那樣學究味道十足(那些學究味道重的解釋,在對一個名詞的解釋過程當中又充滿了一堆讓人抓狂的其餘陌生名詞,不適合初學者)。下面舉一個簡單的例子來講明。閉包

>>>def addx(x):
>>>    def adder(y): return x + y
>>>    return adder
>>> c =  addx(8)
>>> type(c)
<type 'function'>
>>> c.__name__
'adder'
>>> c(10)
18

結合這段簡單的代碼和定義來講明閉包:app

若是在一個內部函數裏:adder(y)就是這個內部函數,函數式編程

對在外部做用域(但不是在全局做用域)的變量進行引用:x就是被引用的變量,x在外部做用域addx裏面,但不在全局做用域裏,函數

則這個內部函數adder就是一個閉包。spa


再稍微講究一點的解釋是,閉包=函數塊+定義函數時的環境,adder就是函數塊,x就是環境,固然這個環境能夠有不少,不止一個簡單的x。code

二,使用閉包注意事項htm

1,閉包中是不能修改外部做用域的局部變量的

>>> def foo():
...     m = 0
...     def foo1():
...         m = 1
...         print m
...
...     print m
...     foo1()
...     print m
...
>>> foo()
0
1
0

從執行結果能夠看出,雖然在閉包裏面也定義了一個變量m,可是其不會改變外部函數中的局部變量m。

2,如下這段代碼是在python中使用閉包時一段經典的錯誤代碼

def foo():
    a = 1
    def bar():
        a = a + 1
        return a
    return bar

這段程序的本意是要經過在每次調用閉包函數時都對變量a進行遞增的操做。但在實際使用時

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

這是由於在執行代碼 c = foo()時,python會導入所有的閉包函數體bar()來分析其的局部變量,python規則指定全部在賦值語句左面的變量都是局部變量,則在閉包bar()中,變量a在賦值符號"="的左面,被python認爲是bar()中的局部變量。再接下來執行print c()時,程序運行至a = a + 1時,由於先前已經把a歸爲bar()中的局部變量,因此python會在bar()中去找在賦值語句右面的a的值,結果找不到,就會報錯。解決的方法很簡單

def foo():
    a = [1]
    def bar():
        a[0] = a[0] + 1
        return a[0]
    return bar

只要將a設定爲一個容器就能夠了。這樣使用起來多少有點不爽,因此在python3之後,在a = a + 1 以前,使用語句nonloacal a就能夠了,該語句顯式的指定a不是閉包的局部變量。

3,還有一個容易產生錯誤的事例也常常被人在介紹python閉包時提起,我一直都沒以爲這個錯誤和閉包有什麼太大的關係,可是它卻是的確是在python函數式編程是容易犯的一個錯誤,我在這裏也不妨介紹一下。先看下面這段代碼

for i in range(3):
    print i

在程序裏面常常會出現這類的循環語句,Python的問題就在於,當循環結束之後,循環體中的臨時變量i不會銷燬,而是繼續存在於執行環境中。還有一個python的現象是,python的函數只有在執行時,纔會去找函數體裏的變量的值。

flist = []
for i in range(3):
    def foo(x): print x + i
    flist.append(foo)
for f in flist:
    f(2)

可能有些人認爲這段代碼的執行結果應該是2,3,4.可是實際的結果是4,4,4。這是由於當把函數加入flist列表裏時,python尚未給i賦值,只有當執行時,再去找i的值是什麼,這時在第一個for循環結束之後,i的值是2,因此以上代碼的執行結果是4,4,4.

解決方法也很簡單,改寫一下函數的定義就能夠了。

for i in range(3):
    def foo(x,y=i): print x + y
    flist.append(foo)


三,做用

說了這麼多,難免有人要問,那這個閉包在實際的開發中有什麼用呢?閉包主要是在函數式開發過程當中使用。如下介紹兩種閉包主要的用途。


用途1,當閉包執行完後,仍然可以保持住當前的運行環境。

好比說,若是你但願函數的每次執行結果,都是基於這個函數上次的運行結果。我以一個相似棋盤遊戲的例子來講明。假設棋盤大小爲50*50,左上角爲座標系原點(0,0),我須要一個函數,接收2個參數,分別爲方向(direction),步長(step),該函數控制棋子的運動。棋子運動的新的座標除了依賴於方向和步長之外,固然還要根據原來所處的座標點,用閉包就能夠保持住這個棋子原來所處的座標。

origin = [0, 0]  # 座標系統原點
legal_x = [0, 50]  # x軸方向的合法座標
legal_y = [0, 50]  # y軸方向的合法座標
def create(pos=origin):
    def player(direction,step):
        # 這裏應該首先判斷參數direction,step的合法性,好比direction不能斜着走,step不能爲負等
        # 而後還要對新生成的x,y座標的合法性進行判斷處理,這裏主要是想介紹閉包,就不詳細寫了。
        new_x = pos[0] + direction[0]*step
        new_y = pos[1] + direction[1]*step
        pos[0] = new_x
        pos[1] = new_y
        #注意!此處不能寫成 pos = [new_x, new_y],緣由在上文有說過
        return pos
    return player

player = create()  # 建立棋子player,起點爲原點
print player([1,0],10)  # 向x軸正方向移動10步
print player([0,1],20)  # 向y軸正方向移動20步
print player([-1,0],10)  # 向x軸負方向移動10步

輸出爲

[10, 0]
[10, 20]
[0, 20]


用途2,閉包能夠根據外部做用域的局部變量來獲得不一樣的結果,這有點像一種相似配置功能的做用,咱們能夠修改外部的變量,閉包根據這個變量展示出不一樣的功能。好比有時咱們須要對某些文件的特殊行進行分析,先要提取出這些特殊行。

 

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進行函數式編程時,閉包對數據的持久化以及按配置產生不一樣的功能,是頗有幫助的。

相關文章
相關標籤/搜索