理解Python中的閉包

來源:互聯網
上載者:User

標籤:javascrip   count()   print   函數式   資料   迴圈   因此   iss   range   

1.定義

  閉包是函數式編程的一個重要的文法結構,函數式編程是一種編程範式 (而面向過程編程和物件導向編程也都是編程範式)。在面向過程編程中,我們見到過函數(function);在物件導向編程中,我們見過對象(object)。函數和對象的根本目的是以某種邏輯方式組織代碼,並提高代碼的可重複使用性(reusability)。閉包也是一種組織代碼的結構,它同樣提高了代碼的可重複使用性。 
  不同程式設計語言實現閉包的方式是不同的,python中閉包從表現形式上看,如果在一個內建函式裡,對在外部範圍(但不是在全域範圍)的變數進行引用,那麼內建函式就被認為是閉包(closure)。 
舉個例子:

1 def outer(x):2     def inner(y):3         return x + y4     return inner 

  結合這段簡單的代碼和定義來說明閉包: 
  inner(y)就是這個內建函式,對在外部範圍(但不是在全域範圍)的變數進行引用:x就是被引用的變數,x在外部範圍outer裡面,但不在全域範圍裡,則這個內建函式inner就是一個閉包。

  再稍微講究一點的解釋是,閉包=函數塊+定義函數時的環境,inner就是函數塊,x就是環境,當然這個環境可以有很多,不止一個簡單的x。

  在函數outer中定義了一個inner函數,inner函數訪問外部函數outer的(參數)變數,並且把inner函數作為傳回值返回給outer函數。

1 a = outer(2)2 print(‘function:‘,a) 3 print(‘result:‘,a(3))

  上面的代碼中a就是一個函數,代碼的執行結果為:

  從結果我們不難看出,a是函數inner而不是outer,這個有點繞,但是並不難理解,因為return回來的是inner函數。

1 print(‘a.func_name‘,a.func_name)

輸出結果為:

  調用函數a,得到的結果是傳入參數的值相加。

  上面的和這句是一樣的:print(‘result:‘,outer(2)(3))

2.使用閉包注意的地方2.1閉包無法修改外部函數的局部變數

如果innerFunc可以修改x的值的話,x的值前後會發生變化,但結果是:

在innerFunc中x的值發生了改變,但是在outerFunc中x的值並未發生變化。

再來看一個例子

2.2閉包無法直接存取外部函數的局部變數
1 def outer():2     x = 5 3     def inner(): #上面一行的x相對inner函數來說是函數外的局部變數(非全域變數)4         x *= x5         return x6     return inner7 8 outer()()

 

運行會報錯: 

解決的方法: 
  1.在python3之前沒有直接的解決方案,只能間接地通過容器類型來解決,因為容器類型不是存放在棧空間的,inner函數可以訪問到。

1 def outer():2     x = [5] 3     def inner(): 4         x[0] *= x[0]5         return x[0]6     return inner 7 8 print(outer()())  #25

 

  2.python3通過nonlocal關鍵字來解決,該語句顯式的指定a不是閉包的局部變數。

1 def outer():2     x = 5 3     def inner(): 4         nonlocal x #把x聲明為非局部變數5         x *= x6         return x7     return inner 8 9 print(outer()())

 

2.3python迴圈中不包含域的概念

  還有一個容易產生錯誤的案例也經常被人在介紹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。loop在python中是沒有域的概念的,flist在像列表中添加func的時候,並沒有儲存i的值,而是當執行f(2)的時候才去取,這時候迴圈已經結束,i的值是2,所以結果都是4。 

  解決方案也很簡單,改寫一下函數的定義就可以了。

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

 

另外一個例子:

需要注意的問題是,返回的函數並沒有立刻執行,而是直到調用了f()才執行。我們來看一個例子:

 1 function count() { 2     var arr = []; 3     for (var i=1; i<=3; i++) { 4         arr.push(function () { 5             return i * i; 6         }); 7     } 8     return arr; 9 }10 11 var results = count();12 var f1 = results[0];13 var f2 = results[1];14 var f3 = results[2];

 

在上面的例子中,每次迴圈,都建立了一個新的函數,然後,把建立的3個函數都添加到一個Array中返回了。

你可能認為調用f1()f2()f3()結果應該是149,但實際結果是:

1 f1(); // 162 f2(); // 163 f3(); // 16

全部都是16!原因就在於返回的函數引用了變數i,但它並非立刻執行。等到3個函數都返回時,它們所引用的變數i已經變成了4,因此最終結果為16

返回閉包時牢記的一點就是:返回函數不要引用任何迴圈變數,或者後續會發生變化的變數。

如果一定要引用迴圈變數怎麼辦?方法是再建立一個函數,用該函數的參數綁定迴圈變數當前的值,無論該迴圈變數後續如何更改,已綁定到函數參數的值不變:

 1 function count() { 2     var arr = []; 3     for (var i=1; i<=3; i++) { 4         arr.push((function (n) { 5             return function () { 6                 return n * n; 7             } 8         })(i)); 9     }10     return arr;11 }12 13 var results = count();14 var f1 = results[0];15 var f2 = results[1];16 var f3 = results[2];17 18 f1(); // 119 f2(); // 420 f3(); // 9

注意這裡用了一個“建立一個匿名函數並立刻執行”的文法:

1 (function (x) {2     return x * x;3 })(3); // 9

理論上講,建立一個匿名函數並立刻執行可以這麼寫:

1 function (x) { return x * x } (3);

但是由於JavaScript文法解析的問題,會報SyntaxError錯誤,因此需要用括弧把整個函數定義括起來:

1 (function (x) { return x * x }) (3);

通常,一個立即執行的匿名函數可以把函數體拆開,一般這麼寫:

1 (function (x) {2     return x * x;3 })(3);
3.閉包的作用

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

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

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

 1 origin = [0, 0]  2 legal_x = [0, 50]   3 legal_y = [0, 50]  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],因為參數變數不能被修改,而pos[]是容器類的解決方案 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 [10, 0] 2  [10, 20]  3  [0, 20] 
用途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") filter_result = filter("result.txt") 

  以上兩種使用情境,用物件導向也是可以很簡單的實現的,但是在用Python進行函數式編程時,閉包對資料的持久化以及按配置產生不同的功能,是很有協助的。

理解Python中的閉包

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.