摘要:在計算機科學中,閉包又稱詞法閉包或函數閉包,是引用了自由變量的函數。閉包被廣泛應用于函數式語言中。運用閉包可以避免對全局變量的使用。將棧頂的元素取出,創建元組,并將該元組進棧。
在計算機科學中,閉包 又稱 詞法閉包 或 函數閉包,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。閉包被廣泛應用于函數式語言中。
從上面這段話中可以看出閉包的兩個重要條件是引用自由變量和函數,與閉包這個名稱結合起來看,這個函數就像是一個包,而這個函數所引用的變量就好比函數這個包中封閉起來的東西,包中的東西被緊緊封閉在包中,函數所引用的變量也被與這個函數所綁定。
首先來看兩個概念 Nonlocal variable 和 Nested function
Nonlocal variable & Nested functionNonlocal variable是相對于某個函數來說的,指的是這個函數所調用的在本函數作用域之外的變量,Nested function指的被定義在一個函數(outer enclosing function)中的函數,這個nested function可以調用包圍它的作用域中的變量。
看一個例子
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 "", line 1, in File " ", line 5, in print_msg File " ", 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)返回一個函數
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仍然能夠正常調用。
>>> del print_msg >>> print_msg("Hello") Traceback (most recent call last): File "閉包的應用", line 1, in 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閉包的內部實現 Code Object
為了了解閉包的內部實現,需要用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__
", line 1>
Cell Object
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 object,但是他們仍然只在各自的作用域下作用。
閉包分析首先反編譯print_msg
>>> dis.dis(print_msg)
2 0 LOAD_CLOSURE 0 (msg)
2 BUILD_TUPLE 1
4 LOAD_CONST 1 (", line 2>)
6 LOAD_CONST 2 ("print_msg..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函數,從而達到了閉包中內部函數訪問外部函數變量的效果。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/38562.html
Python 中通過函數定義所創建的用戶自定義函數對象均具有一些特殊屬性,需要注意的是這里介紹的是自定義函數(function類型)的特殊屬性,而非方法(method 類型)的特殊屬性,函數和方法的特熟屬性以及默認的返回值可能不盡相同。 對于大多數特殊屬性,可以通過下面這個例子示范一下: class Test(): def func(self, v = dog): 這里演...
摘要:項目地址閉包在計算機科學中,閉包英語,又稱詞法閉包或函數閉包,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。 項目地址:https://git.io/pytips 閉包(Closure) 在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是...
摘要:閉包可以用來在一個函數與一組私有變量之間創建關聯關系。夾帶私貨外部變量返回的是函數,帶私貨的函數支持將函數當成對象使用的編程語言,一般都支持閉包。所以說當你的裝飾器需要自定義參數時,一般都會形成閉包。 Python中的閉包不是一個一說就能明白的概念,但是隨著你往學習的深入,無論如何你都需要去了解這么一個東西。 閉包的概念 我們嘗試從概念上去理解一下閉包。 在一些語言中,在函數中可以(嵌...
摘要:我們說觸發了閉包的函數叫做閉包函數閉包最大的特點就是它可以被外層函數返回后賦值給一個變量,并且攜帶了外層函數內定義的變量例子如下變量為函數開辟的局部命名空間內定義的變量函數內引用了變量的內層函數名被當作返回值,此時閉包規則達成。 什么是閉包? 其實我們在使用函數過程中不經意間就會觸發閉包,因為總會出于某種原因會在函數內引用或修改上一層函數的變量,這時就會觸發閉包 那么什么是閉包?其實就...
摘要:恩如期來啦閉包一函數作為返回值介紹閉包之前,先了解一下函數作為返回值的情況。例如之前介紹的裝飾器中,就出現了將函數作為返回值。當執行時,相當于執行,且包含。允許使用關鍵字創造匿名函數。例如調用默認可以把匿名函數作為返回值返回,例如 恩~ 如期來啦閉包~ 一、函數作為返回值 介紹閉包之前,先了解一下函數作為返回值的情況。高階函數除了可以接收函數作為參數外,還可以把函數作為結果值返回。...
閱讀 3324·2021-11-23 09:51
閱讀 2449·2021-11-09 09:46
閱讀 1482·2019-08-30 15:54
閱讀 3129·2019-08-30 14:22
閱讀 2914·2019-08-29 12:40
閱讀 1636·2019-08-26 10:33
閱讀 1784·2019-08-23 17:09
閱讀 1560·2019-08-23 16:11