Python-闭包详解
在函數(shù)編程中經(jīng)常用到閉包。閉包是什么,它是怎么產(chǎn)生的及用來解決什么問題呢。給出字面的定義先:閉包是由函數(shù)及其相關(guān)的引用環(huán)境組合而成的實體(即:閉包=函數(shù)+引用環(huán)境)(想想Erlang的外層函數(shù)傳入一個參數(shù)a, 內(nèi)層函數(shù)依舊傳入一個參數(shù)b, 內(nèi)層函數(shù)使用a和b, 最后返回內(nèi)層函數(shù))。這個從字面上很難理解,特別對于一直使用命令式語言進(jìn)行編程的程序員們。本文將結(jié)合實例代碼進(jìn)行解釋。
函數(shù)是什么
地球人都知道:函數(shù)只是一段可執(zhí)行代碼,編譯后就“固化”了,每個函數(shù)在內(nèi)存中只有一份實例,得到函數(shù)的入口點便可以執(zhí)行函數(shù)了。在函數(shù)式編程語言中,函 數(shù)是一等公民(First class value:第一類對象,我們不需要像命令式語言中那樣借助函數(shù)指針,委托操作函數(shù)),函數(shù)可以作為另一個函數(shù)的參數(shù)或返回值,可以賦給一個變量。函數(shù)可 以嵌套定義,即在一個函數(shù)內(nèi)部可以定義另一個函數(shù),有了嵌套函數(shù)這種結(jié)構(gòu),便會產(chǎn)生閉包問題。如:
?????sum=n
?????def?InsFunc():
?????????????return?sum+1
?????return?InsFunc
>>>?myFunc=ExFunc(10)
>>>?myFunc()
11
>>>?myAnotherFunc=ExFunc(20)
>>>?myAnotherFunc()
21
>>>?myFunc()
11
>>>?myAnotherFunc()
21
>>>
在這段程序中,函數(shù)InsFunc是函數(shù)ExFunc的內(nèi)嵌函數(shù),并且是ExFunc函數(shù)的返回值。我們注意到一個問題:內(nèi)嵌函數(shù)InsFunc中 引用到外層函數(shù)中的局部變量sum,IronPython會這么處理這個問題呢?先讓我們來看看這段代碼的運(yùn)行結(jié)果。當(dāng)我們調(diào)用分別由不同的參數(shù)調(diào)用 ExFunc函數(shù)得到的函數(shù)時(myFunc(),myAnotherFunc()),得到的結(jié)果是隔離的,也就是說每次調(diào)用ExFunc函數(shù)后都將生成并保存一個新的局部變量sum。其實這里ExFunc函數(shù)返回的就是閉包。
引用環(huán)境
按照命令式語言的規(guī)則,ExFunc函數(shù)只是返回了內(nèi)嵌函數(shù)InsFunc的地址,在執(zhí)行InsFunc函數(shù)時將會由于在其作用域內(nèi)找不到sum變量而出 錯。而在函數(shù)式語言中,當(dāng)內(nèi)嵌函數(shù)體內(nèi)引用到體外的變量時,將會把定義時涉及到的引用環(huán)境和函數(shù)體打包成一個整體(閉包)返回。現(xiàn)在給出引用環(huán)境的定義就 容易理解了:引用環(huán)境是指在程序執(zhí)行中的某個點所有處于活躍狀態(tài)的約束(一個變量的名字和其所代表的對象之間的聯(lián)系)所組成的集合。閉包的使用和正常的函 數(shù)調(diào)用沒有區(qū)別。
由于閉包把函數(shù)和運(yùn)行時的引用環(huán)境打包成為一個新的整體,所以就解決了函數(shù)編程中的嵌套所引發(fā)的問題。如上述代碼段中,當(dāng)每次調(diào)用ExFunc函數(shù) 時都將返回一個新的閉包實例,這些實例之間是隔離的,分別包含調(diào)用時不同的引用環(huán)境現(xiàn)場。不同于函數(shù),閉包在運(yùn)行時可以有多個實例,不同的引用環(huán)境和相同 的函數(shù)組合可以產(chǎn)生不同的實例。
?一,定義
python中的閉包從表現(xiàn)形式上定義(解釋)為:如果在一個內(nèi)部函數(shù)里,對在外部作用域(但不是在全局作用域)的變量進(jìn)行引用,那么內(nèi)部函數(shù)就被認(rèn)為是閉包(closure).這個定義是相對直白的,好理解的,不像其他定義那樣學(xué)究味道十足(那些學(xué)究味道重的解釋,在對一個名詞的解釋過程中又充滿了一堆讓人抓狂的其他陌生名詞,不適合初學(xué)者)。下面舉一個簡單的例子來說明。
結(jié)合這段簡單的代碼和定義來說明閉包:
如果在一個內(nèi)部函數(shù)里:adder(y)就是這個內(nèi)部函數(shù),
對在外部作用域(但不是在全局作用域)的變量進(jìn)行引用:x就是被引用的變量,x在外部作用域addx里面,但不在全局作用域里,
則這個內(nèi)部函數(shù)adder就是一個閉包。
再稍微講究一點的解釋是,閉包=函數(shù)塊+定義函數(shù)時的環(huán)境,adder就是函數(shù)塊,x就是環(huán)境,當(dāng)然這個環(huán)境可以有很多,不止一個簡單的x。
二,使用閉包注意事項
1,閉包中是不能修改外部作用域的局部變量的
從執(zhí)行結(jié)果可以看出,雖然在閉包里面也定義了一個變量m,但是其不會改變外部函數(shù)中的局部變量m。
2,以下這段代碼是在python中使用閉包時一段經(jīng)典的錯誤代碼
這段程序的本意是要通過在每次調(diào)用閉包函數(shù)時都對變量a進(jìn)行遞增的操作。但在實際使用時
>>> 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會導(dǎo)入全部的閉包函數(shù)體bar()來分析其的局部變量,python規(guī)則指定所有在賦值語句左面的變量都是局部變量,則在閉包bar()中,變量a在賦值符號"="的左面,被python認(rèn)為是bar()中的局部變量。再接下來執(zhí)行print c()時,程序運(yùn)行至a = a + 1時,因為先前已經(jīng)把a(bǔ)歸為bar()中的局部變量,所以python會在bar()中去找在賦值語句右面的a的值,結(jié)果找不到,就會報錯。解決的方法很簡單
def foo(): a = [1] def bar(): a[0] = a[0] + 1 return a[0] return bar只要將a設(shè)定為一個容器就可以了。這樣使用起來多少有點不爽,所以在python3以后,在a = a + 1 之前,使用語句nonloacal a就可以了,該語句顯式的指定a不是閉包的局部變量。
3,還有一個容易產(chǎn)生錯誤的事例也經(jīng)常被人在介紹python閉包時提起,我一直都沒覺得這個錯誤和閉包有什么太大的關(guān)系,但是它倒是的確是在python函數(shù)式編程是容易犯的一個錯誤,我在這里也不妨介紹一下。先看下面這段代碼
在程序里面經(jīng)常會出現(xiàn)這類的循環(huán)語句,Python的問題就在于,當(dāng)循環(huán)結(jié)束以后,循環(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)可能有些人認(rèn)為這段代碼的執(zhí)行結(jié)果應(yīng)該是2,3,4.但是實際的結(jié)果是4,4,4。這是因為當(dāng)把函數(shù)加入flist列表里時,python還沒有給i賦值,只有當(dāng)執(zhí)行時,再去找i的值是什么,這時在第一個for循環(huán)結(jié)束以后,i的值是2,所以以上代碼的執(zhí)行結(jié)果是4,4,4.
解決方法也很簡單,改寫一下函數(shù)的定義就可以了。
三,作用
說了這么多,不免有人要問,那這個閉包在實際的開發(fā)中有什么用呢?閉包主要是在函數(shù)式開發(fā)過程中使用。以下介紹兩種閉包主要的用途。
用途1,當(dāng)閉包執(zhí)行完后,仍然能夠保持住當(dāng)前的運(yùn)行環(huán)境。
比如說,如果你希望函數(shù)的每次執(zhí)行結(jié)果,都是基于這個函數(shù)上次的運(yùn)行結(jié)果。我以一個類似棋盤游戲的例子來說明。假設(shè)棋盤大小為50*50,左上角為坐標(biāo)系原點(0,0),我需要一個函數(shù),接收2個參數(shù),分別為方向(direction),步長(step),該函數(shù)控制棋子的運(yùn)動。棋子運(yùn)動的新的坐標(biāo)除了依賴于方向和步長以外,當(dāng)然還要根據(jù)原來所處的坐標(biāo)點,用閉包就可以保持住這個棋子原來所處的坐標(biāo)。
輸出為
[10, 0] [10, 20] [0, 20]用途2,閉包可以根據(jù)外部作用域的局部變量來得到不同的結(jié)果,這有點像一種類似配置功能的作用,我們可以修改外部的變量,閉包根據(jù)這個變量展現(xiàn)出不同的功能。比如有時我們需要對某些文件的特殊行進(jì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"關(guān)鍵字的行,則可以這樣使用例子程序
filter = make_filter("pass") filter_result = filter("result.txt")以上兩種使用場景,用面向?qū)ο笠彩强梢院芎唵蔚膶崿F(xiàn)的,但是在用Python進(jìn)行函數(shù)式編程時,閉包對數(shù)據(jù)的持久化以及按配置產(chǎn)生不同的功能,是很有幫助的。
總結(jié)
以上是生活随笔為你收集整理的Python-闭包详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 32bit 改为64bit
- 下一篇: js实现日期转换方法