Python中的閉包例項詳解

2021-07-25 03:43:46 字數 4365 閱讀 9811

這篇文章主要介紹了python中的閉包,針對閉包的定義、用法及注意事項進行了例項講解,有助於讀者深入理解閉包的概念及用法,需要的朋友可以參考下

一般來說閉包這個概念在很多語言中都有涉及,本文主要談談python中的閉包定義及相關用法。python中使用閉包主要是在進行函式式開發時使用。詳情分析如下:

一、定義

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

>>>defaddx(x):

>>> defadder(y): return x + y

>>> returnadder

>>> c = addx(8)

>>>type(c)

>>>c.__name__

'adder'

>>> c(10) 18

結合這段簡單的**和定義來說明閉包:

如果在乙個內部函式裡:adder(y)就是這個內部函式,

對在外部作用域(但不是在全域性作用域)的變數進行引用:x就是被引用的變數,x在外部作用域addx裡面,但不在全域性作用域裡,

則這個內部函式adder就是乙個閉包。

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

二、使用閉包注意事項

1.閉包中是不能修改外部作用域的區域性變數的

>>> deffoo():

...  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()

>>> printc()

traceback (mostrecent call last):

file "", line 1, in

file "", 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 之前,使用語句nonlocal 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

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

三、作用

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

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

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

origin = [0, 0] #

座標系統原點

legal_x = [0, 50] #x

軸方向的合法座標

legal_y = [0, 50] #y

軸方向的合法座標

defcreate(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步

printplayer([0,1],20) #

向y軸正方向移動20步

printplayer([-1,0],10) #

向x軸負方向移動10步

輸出為:

[10, 0]

[10, 20]

[0, 20]

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

defmake_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進行函式式程式設計時,閉包對資料的持久化以及按配置產生不同的功能,是很有幫助的。

python閉包的例項詳解

1 在外部函式中定義內部函式,內部函式包含訪問外部函式。即使外部函式的生命週期結束後,內部函式仍然可以訪問外部函式變數。2 外部函式的返回值是內部函式本身。def outer cheer hello def inner name return cheer name return inner if n...

python閉包詳解 例項演示

python閉包詳解 例項演示 閉包 這個詞語相信大多數學過程式設計的同學並不陌生,但是有時候理解起來還是有一定難度。先看定義 閉包是由函式和與其相關的引用環境組合而成的實體。比如參考資源中就有這樣的的定義 在實現深約束時,需要建立乙個能顯式表示引用環境的東西,並將它與相關的子程式 在一起,這樣 起...

詳解Python中的閉包

定義 python中閉包的定義是 在乙個內部函式裡,對在外部作用域 非全域性作用域 的變數進行引用,則內部函式就被認為是閉包 closure 我們來結合 看一下閉包的概念 在函式addx 內部存在函式addy 因此,addy 就是內部函式。下面對函式addx 呼叫 我們可以發現addx 8 的型別是...