在計算機科學中,閉包 又稱 詞法閉包 或 函數閉包,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即便已經離開了創造它的環境也不例外。閉包被普遍應用於函數式語言中。閉包
從上面這段話中能夠看出閉包的兩個重要條件是引用自由變量和函數,與閉包這個名稱結合起來看,這個函數就像是一個包,而這個函數所引用的變量就比如函數這個包中封閉起來的東西,包中的東西被牢牢封閉在包中,函數所引用的變量也被與這個函數所綁定。app
首先來看兩個概念 Nonlocal variable 和 Nested function函數
Nonlocal variable
是相對於某個函數來講的,指的是這個函數所調用的在本函數做用域以外的變量,Nested function
指的被定義在一個函數(outer enclosing function)中的函數,這個nested function
能夠調用包圍它的做用域中的變量。code
看一個例子orm
def print_msg(msg): # outer enclosing function def printer(): # nested function print(msg) printer() >>> print_msg("Hello") Hello
在這個例子中函數printer
就是一個nested function
,而變量msg
就是一個nonlocal variable
。這裏須要注意的是,printer
雖然能夠訪問msg
,可是不能夠改變它,若是嘗試更改會出現UnboundLocalError: local variable 'msg' referenced before assignment
。對象
def print_msg(msg): def printer(): msg += 'a' print(msg) printer() >>> print_msg("Hello") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in print_msg File "<stdin>", line 3, in printer UnboundLocalError: local variable 'msg' referenced before assignment local variable 'msg' referenced before assignment
若是必需要更改這個變量的值,在Python3中新引入的nonlocal
語句能夠解決。資源
def print_msg(msg): def printer(): nonlocal msg msg += 'a' print(msg) printer() >>> print_msg("Hello") Helloa
在Python2中使用global
也可解決,可是global
會直接查找全局變量,而nonlocal
則是按優先級從本地-->全局
進行搜索。作用域
下面使外層函數(outer enclosing function)返回一個函數it
def print_msg(msg): def printer(): print(msg) return printer >>> another = print_msg("Hello") >>> another() Hello
將print_msg("Hello")
返回的函數賦值給another
,再調用another
函數時,發現已經離開了print_msg
函數的做用域,可是"Hello"
已經被綁定給another
,因此仍然可以正常調用,這就是Python中的閉包。
刪除print_msg
以後,another
仍然可以正常調用。io
>>> del print_msg >>> print_msg("Hello") Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'print_msg' is not defined name 'print_msg' is not defined >>> another() Hello
當符合下面幾個條件時就造成了閉包:
有一個Nested function
這個Nested function
訪問了父函數做用域中的變量
父函數返回了這個Nested function
閉包主要運用在須要講父函數做用域中的變量綁定到子函數的場景之中,在釋放掉父函數以後子函數也不會受到影響。運用閉包能夠避免對全局變量的使用。對於一個只有須要實現少數方法的類咱們也能夠用閉包來替代,這樣作能夠減小資源的使用。
下面須要用類定義不一樣動物的叫聲
class Animal: def __init__(self, animal): self.animal = animal def sing(self, voice): return "{} sings {}".format(self.animal, voice) >>> dog = Animal("dog") >>> cow = Animal("cow") >>> dog.sing("wong") 'dog sings wong' >>> cow.sing("mow") cow sings mow'
用閉包替代
def make_sing(animal): def make_voice(voice): return "{} sings {}".format(animal, voice) return make_voice >>> dog = make_sing("dog") >>> dog("wong") 'dog sings wong' >>> cow = make_sing("cow") >>> cow("mow") 'cow sings mow'
閉包一般用來實現一個通用的功能,Python中的裝飾器就是對閉包的一種應用,只不過裝飾器中父函數的參數是一個函數,下面這個例子經過裝飾器實現了在子函數執行先後輸出提示信息。
def make_wrap(func): def wrapper(*args): print("before function") func(*args) print("after function") return wrapper @make_wrap def print_msg(msg): print(msg) >>> print_msg("Hello") before function Hello after function
裝飾器也能夠進行疊加
def make_another(func): def wrapper(*args): print("another begin") func(*args) print("another end") return wrapper @make_another @make_wrap def print_msg(msg): print(msg) >>> print_msg("Hello") another begin before function Hello after function another end
爲了瞭解閉包的內部實現,須要用compile
命令得出相應的code object
>>> code_obj = compile("print_msg('Hello')", "", "single")
這裏第一個參數是一個能夠被exec
或 eval
解析的模塊、語句或者表達式;
第二個參數是用來存放運行時錯誤的文件;
第三個選擇single
模式,與前面第一個參數填寫的表達式相匹配,若是第一個參數是表達式則須要用eval
模式,若是是模塊則應該用exec
模式。
下面經過dis
講code_obj
反編譯成助記符
>>> dis.dis(code_obj) 1 0 LOAD_NAME 0 (print_msg) 2 LOAD_CONST 0 ('Hello') 4 CALL_FUNCTION 1 6 PRINT_EXPR 8 LOAD_CONST 1 (None) 10 RETURN_VALUE
Python3中經過__code__
訪問函數的code object
(Python2中爲func_code
)
>>> print_msg.__code__ <code object print_msg at 0x10d5c7300, file "<stdin>", line 1>
cell object
用來存儲被多個做用域所引用的變量。
好比下面函數中msg
被print_msg
所引用,也被printer
所引用,因此msg
會被存在一個cell object
中
def print_msg(msg): def printer(): print(msg) return printer
查看其__closure__
屬性能夠驗證咱們的想法
>>> print_msg("Hello").__closure__ (<cell at 0x10d121d38: str object at 0x10d4a6f48>,)
儘管這兩個引用都被存在贊成個cell object
,可是他們仍然只在各自的做用域下做用。
首先反編譯print_msg
>>> dis.dis(print_msg) 2 0 LOAD_CLOSURE 0 (msg) 2 BUILD_TUPLE 1 4 LOAD_CONST 1 (<code object printer at 0x10d5c7780, file "<stdin>", line 2>) 6 LOAD_CONST 2 ('print_msg.<locals>.printer') 8 MAKE_FUNCTION 8 10 STORE_FAST 1 (printer) 4 12 LOAD_FAST 1 (printer) 14 RETURN_VALUE
LOAD_CLOSURE 0 (msg)
將變量msg
進棧。
BUILD_TUPLE 1
將棧頂的元素取出,建立元組,並將該元組push進棧。
LOAD_CONST 1
從print_msg.__code__.co_consts[1]
中取出,爲printer
的code object
的地址,將其push進棧。
LOAD_CONST 2
從print_msg.__code__.co_consts[2]
中取出,將其push進棧。
STORE_FAST 1
從棧頂取出以前建立的函數對象的地址信息賦給局部變量printer(局部變量名記錄在__code__.co_varnames中)
__code__.co_varnames的內容爲('msg','printer')
將變量msg(記錄在__code__.co_cellvars[0])綁定棧頂的函數對象地址。
LOAD_FAST 1
將msg
的值壓入棧。
RETURN_VALUE
返回棧頂。
能夠看到在STORE_FAST 1
中將變量msg
綁定到了printer
函數,從而達到了閉包中內部函數訪問外部函數變量的效果。