python的閉包

文章轉載來自http://blog.csdn.net/marty_fu/article/details/7679297

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

一,定義

python中的閉包從表現形式上定義(解釋)爲:如果在一個內部函數裏,對在外部作用域(但不是在全局作用域)的變量進行引用,那麼內部函數就被認爲是閉包(closure).這個定義是相對直白的,好理解的,不像其他定義那樣學究味道十足(那些學究味道重的解釋,在對一個名詞的解釋過程中又充滿了一堆讓人抓狂的其他陌生名詞,不適合初學者)。下面舉一個簡單的例子來說明。
  1. >>>def addx(x):  
  2. >>>    def adder(y): return x + y  
  3. >>>    return adder  
  4. >>> c =  addx(8)  
  5. >>> type(c)  
  6. <type 'function'>  
  7. >>> c.__name__  
  8. 'adder'  
  9. >>> c(10)  
  10. 18  
結合這段簡單的代碼和定義來說明閉包:
如果在一個內部函數裏:adder(y)就是這個內部函數,
對在外部作用域(但不是在全局作用域)的變量進行引用:x就是被引用的變量,x在外部作用域addx裏面,但不在全局作用域裏,
則這個內部函數adder就是一個閉包。

再稍微講究一點的解釋是,閉包=函數塊+定義函數時的環境,adder就是函數塊,x就是環境,當然這個環境可以有很多,不止一個簡單的x。
二,使用閉包注意事項
1,閉包中是不能修改外部作用域的局部變量的
  1. >>> def foo():  
  2. ...     m = 0  
  3. ...     def foo1():  
  4. ...         m = 1  
  5. ...         print m  
  6. ...  
  7. ...     print m  
  8. ...     foo1()  
  9. ...     print m  
  10. ...  
  11. >>> foo()  
  12. 0  
  13. 1  
  14. 0  
從執行結果可以看出,雖然在閉包裏面也定義了一個變量m,但是其不會改變外部函數中的局部變量m。

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

  1. def foo():  
  2.     a = 1  
  3.     def bar():  
  4.         a = a + 1  
  5.         return a  
  6.     return bar  
這段程序的本意是要通過在每次調用閉包函數時都對變量a進行遞增的操作。但在實際使用時

  1. >>> c = foo()  
  2. >>> print c()  
  3. Traceback (most recent call last):  
  4.   File "<stdin>", line 1, in <module>  
  5.   File "<stdin>", line 4, in bar  
  6. 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的值,結果找不到,就會報錯。解決的方法很簡單

  1. def foo():  
  2.     a = [1]  
  3.     def bar():  
  4.         a[0] = a[0] + 1  
  5.         return a[0]  
  6.     return bar  
只要將a設定爲一個容器就可以了。這樣使用起來多少有點不爽,所以在python3以後,在a = a + 1 之前,使用語句nonloacal a就可以了,該語句顯式的指定a不是閉包的局部變量。

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

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

  1. flist = []  
  2. for i in range(3):  
  3.     def foo(x): print x + i  
  4.     flist.append(foo)  
  5. for f in flist:  
  6.     f(2)  
可能有些人認爲這段代碼的執行結果應該是2,3,4.但是實際的結果是4,4,4。這是因爲當把函數加入flist列表裏時,python還沒有給i賦值,只有當執行時,再去找i的值是什麼,這時在第一個for循環結束以後,i的值是2,所以以上代碼的執行結果是4,4,4.
解決方法也很簡單,改寫一下函數的定義就可以了。
  1. for i in range(3):  
  2.     def foo(x,y=i): print x + y  
  3.     flist.append(foo)  

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

用途1,當閉包執行完後,仍然能夠保持住當前的運行環境。
比如說,如果你希望函數的每次執行結果,都是基於這個函數上次的運行結果。我以一個類似棋盤遊戲的例子來說明。假設棋盤大小爲50*50,左上角爲座標系原點(0,0),我需要一個函數,接收2個參數,分別爲方向(direction),步長(step),該函數控制棋子的運動。棋子運動的新的座標除了依賴於方向和步長以外,當然還要根據原來所處的座標點,用閉包就可以保持住這個棋子原來所處的座標。
  1. origin = [00]  # 座標系統原點  
  2. legal_x = [050]  # x軸方向的合法座標  
  3. legal_y = [050]  # y軸方向的合法座標  
  4. def create(pos=origin):  
  5.     def player(direction,step):  
  6.         # 這裏應該首先判斷參數direction,step的合法性,比如direction不能斜着走,step不能爲負等  
  7.         # 然後還要對新生成的x,y座標的合法性進行判斷處理,這裏主要是想介紹閉包,就不詳細寫了。  
  8.         new_x = pos[0] + direction[0]*step  
  9.         new_y = pos[1] + direction[1]*step  
  10.         pos[0] = new_x  
  11.         pos[1] = new_y  
  12.         #注意!此處不能寫成 pos = [new_x, new_y],原因在上文有說過  
  13.         return pos  
  14.     return player  
  15.   
  16. player = create()  # 創建棋子player,起點爲原點  
  17. print player([1,0],10)  # 向x軸正方向移動10步  
  18. print player([0,1],20)  # 向y軸正方向移動20步  
  19. print player([-1,0],10)  # 向x軸負方向移動10步  
輸出爲

  1. [100]  
  2. [1020]  
  3. [020]  

用途2,閉包可以根據外部作用域的局部變量來得到不同的結果,這有點像一種類似配置功能的作用,我們可以修改外部的變量,閉包根據這個變量展現出不同的功能。比如有時我們需要對某些文件的特殊行進行分析,先要提取出這些特殊行。

  1. def make_filter(keep):  
  2.     def the_filter(file_name):  
  3.         file = open(file_name)  
  4.         lines = file.readlines()  
  5.         file.close()  
  6.         filter_doc = [i for i in lines if keep in i]  
  7.         return filter_doc  
  8.     return the_filter  
如果我們需要取得文件"result.txt"中含有"pass"關鍵字的行,則可以這樣使用例子程序

  1. filter = make_filter("pass")  
  2. filter_result = filter("result.txt")  
以上兩種使用場景,用面向對象也是可以很簡單的實現的,但是在用Python進行函數式編程時,閉包對數據的持久化以及按配置產生不同的功能,是很有幫助的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章