Python中的閉包實例詳解
一般來說閉包這個概念在很多語言中都有涉及,本文主要談談python中的閉包定義及相關用法。Python中使用閉包主要是在進行函數(shù)式開發(fā)時使用。詳情分析如下:
一、定義
python中的閉包從表現(xiàn)形式上定義(解釋)為:如果在一個內(nèi)部函數(shù)里,對在外部作用域(但不是在全局作用域)的變量進行引用,那么內(nèi)部函數(shù)就被認為是閉包(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
結合這段簡單的代碼和定義來說明閉包:
如果在一個內(nèi)部函數(shù)里:adder(y)就是這個內(nèi)部函數(shù),
對在外部作用域(但不是在全局作用域)的變量進行引用:x就是被引用的變量,x在外部作用域addx里面,但不在全局作用域里,
則這個內(nèi)部函數(shù)adder就是一個閉包。
再稍微講究一點的解釋是,閉包=函數(shù)塊+定義函數(shù)時的環(huán)境,adder就是函數(shù)塊,x就是環(huán)境,當然這個環(huán)境可以有很多,不止一個簡單的x。
二、使用閉包注意事項
1.閉包中是不能修改外部作用域的局部變量的
>>> def foo(): ... m = 0 ... def foo1(): ... m = 1 ... print m ... ... print m ... foo1() ... print m ... >>> foo() 0 1 0
從執(zhí)行結果可以看出,雖然在閉包里面也定義了一個變量m,但是其不會改變外部函數(shù)中的局部變量m。
2.以下這段代碼是在python中使用閉包時一段經(jīng)典的錯誤代碼
def foo(): a = 1 def bar(): a = a + 1 return a return bar
這段程序的本意是要通過在每次調用閉包函數(shù)時都對變量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
這是因為在執(zhí)行代碼 c = foo()時,python會導入全部的閉包函數(shù)體bar()來分析其的局部變量,python規(guī)則指定所有在賦值語句左面的變量都是局部變量,則在閉包bar()中,變量a在賦值符號"="的左面,被python認為是bar()中的局部變量。再接下來執(zhí)行print c()時,程序運行至a = a + 1時,因為先前已經(jīng)把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.還有一個容易產(chǎn)生錯誤的事例也經(jīng)常被人在介紹python閉包時提起,我一直都沒覺得這個錯誤和閉包有什么太大的關系,但是它倒是的確是在python函數(shù)式編程是容易犯的一個錯誤,我在這里也不妨介紹一下。先看下面這段代碼
for i in range(3): print i
在程序里面經(jīng)常會出現(xiàn)這類的循環(huán)語句,Python的問題就在于,當循環(huán)結束以后,循環(huán)體中的臨時變量i不會銷毀,而是繼續(xù)存在于執(zhí)行環(huán)境中。還有一個python的現(xiàn)象是,python的函數(shù)只有在執(zhí)行時,才會去找函數(shù)體里的變量的值。
flist = [] for i in range(3): def foo(x): print x + i flist.append(foo) for f in flist: f(2)
可能有些人認為這段代碼的執(zhí)行結果應該是2,3,4.但是實際的結果是4,4,4。這是因為當把函數(shù)加入flist列表里時,python還沒有給i賦值,只有當執(zhí)行時,再去找i的值是什么,這時在第一個for循環(huán)結束以后,i的值是2,所以以上代碼的執(zhí)行結果是4,4,4.
解決方法也很簡單,改寫一下函數(shù)的定義就可以了。
for i in range(3): def foo(x,y=i): print x + y flist.append(foo)
三、作用
說了這么多,不免有人要問,那這個閉包在實際的開發(fā)中有什么用呢?閉包主要是在函數(shù)式開發(fā)過程中使用。以下介紹兩種閉包主要的用途。
用途1:當閉包執(zhí)行完后,仍然能夠保持住當前的運行環(huán)境。
比如說,如果你希望函數(shù)的每次執(zhí)行結果,都是基于這個函數(shù)上次的運行結果。我以一個類似棋盤游戲的例子來說明。假設棋盤大小為50*50,左上角為坐標系原點(0,0),我需要一個函數(shù),接收2個參數(shù),分別為方向(direction),步長(step),該函數(shù)控制棋子的運動。棋子運動的新的坐標除了依賴于方向和步長以外,當然還要根據(jù)原來所處的坐標點,用閉包就可以保持住這個棋子原來所處的坐標。
origin = [0, 0] # 坐標系統(tǒng)原點 legal_x = [0, 50] # x軸方向的合法坐標 legal_y = [0, 50] # y軸方向的合法坐標 def create(pos=origin): def player(direction,step): # 這里應該首先判斷參數(shù)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() # 創(chuàng)建棋子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:閉包可以根據(jù)外部作用域的局部變量來得到不同的結果,這有點像一種類似配置功能的作用,我們可以修改外部的變量,閉包根據(jù)這個變量展現(xiàn)出不同的功能。比如有時我們需要對某些文件的特殊行進行分析,先要提取出這些特殊行。
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")
以上兩種使用場景,用面向對象也是可以很簡單的實現(xiàn)的,但是在用Python進行函數(shù)式編程時,閉包對數(shù)據(jù)的持久化以及按配置產(chǎn)生不同的功能,是很有幫助的。
相信本文所述對大家的Python程序設計有一定的借鑒價值。
相關文章
幾行代碼讓 Python 函數(shù)執(zhí)行快 30 倍
Python 編程語言,與其他流行編程語言相比主要缺點是它的動態(tài)特性和多功能屬性拖慢了速度表現(xiàn)。Python 代碼是在運行時被解釋的,而不是在編譯時被編譯為原生代碼。在本文中,我們將討論如何用多處理模塊并行執(zhí)行自定義 Python 函數(shù),并進一步對比運行時間指標。2021-10-10Python實現(xiàn)爆破ZIP文件(支持純數(shù)字,數(shù)字+字母,密碼本)
這篇文章主要為大家分享了如何利用Python實現(xiàn)破解zip文件的密碼,能實現(xiàn)破解純數(shù)字、數(shù)字+字母、密碼本等種類的密碼,需要的可以參考一下2022-03-03用Python解析身份證號獲取年齡和性別的實現(xiàn)方法
身份證號碼包含了豐富的信息,包括生日和性別,Python提供了處理和解析身份證號的功能,讓我們能夠從中提取出相關的信息,本文將介紹如何利用Python解析身份證號,獲取持有者的年齡和性別信息,感興趣的朋友可以參考下2023-12-12