本篇內(nèi)容介紹了“Python 中閉包概念是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、成都小程序開發(fā)、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了淮濱免費建站歡迎大家使用!
計算機中有些英文專業(yè)詞匯,字面直譯,難免因缺少上下文而顯得蒼白拗口,須得多方鋪墊,方能味得古怪下面的原理。閉包(closure)便是一個這樣牽扯了許多上下文的概念,包括編程語言最基本的綁定(binding),環(huán)境(environments),變量作用域(scope)以及函數(shù)是第一等公民(function as the first-class)等等。
在Python中,binding(綁定) 是編程語言最基本的抽象手法,它將一個值綁定到一個變量上,并且稍后可以引用或者修改該變量。下面是幾種不同層次的綁定,每組語句在運行時將一個名字與對應(yīng)值綁定到其定義所在的環(huán)境中。
In [1]: square = 4
In [1]: def square(x):
return x*x
In [1]: class square:
def __init__(self, x):
self.x = x
def value(self):
return self.x * self.x
依照執(zhí)行順序,同名多次綁定,后面會覆蓋前面:
In [1]: square = 3
In [2]: square
Out[2]: 3
In [3]: def square(x):
...: return x * x
...:
...:
In [4]: square
Out[4]:
In [5]: class square:
...: def __init__(self, x):
...: self.x = x
...:
In [6]: square
Out[6]: __main__.square
說這些都是抽象,是因為它們提供了對數(shù)據(jù)、復(fù)合操作或數(shù)據(jù)集合的封裝手段,即將一個名稱與復(fù)雜的數(shù)據(jù)或邏輯進行捆綁,使調(diào)用者不用關(guān)心其實現(xiàn)細節(jié),并可以據(jù)此來構(gòu)建更復(fù)雜的工程??梢哉f綁定是編程的基石。
回到本文的主題上來,閉包是對一組復(fù)合語句的抽象,也就是函數(shù),只不過是一種特殊的函數(shù),至于這個特殊性在哪,這里先賣個關(guān)子,等稍后引入更多概念后再進行闡述。
scope(作用域),顧名思義,也就是某個binding 能罩多大的范圍,或者說可以在多大范圍內(nèi)訪問的到一個變量。每個函數(shù)定義會生成一個局部定義域。
Python,和大多數(shù)編程語言一樣,使用的是靜態(tài)作用域(static scoping,有時也稱 lexical scoping)規(guī)則。在函數(shù)嵌套定義的時候,內(nèi)層函數(shù)內(nèi)可以訪問外層函數(shù)的變量值。因此你可以把作用域想象成一個容器,即它是可以嵌套的,并且內(nèi)層作用域會擴展外層作用域,而最外層作用域即全局作用域。
上一小節(jié)提到了,多次同名綁定,后面會覆蓋先前,其實有隱含前提:在同一作用域內(nèi)。如果是嵌套作用域,其實是隱藏的關(guān)系,內(nèi)層函數(shù)的變量定義會遮蔽外層函數(shù)同一名字定義,但是在外層作用域中,該變量仍是原值:
In [16]: a = 4
In [17]: def outer():
...: a = 5
...: print(a)
...: def inner():
...: a = 6
...: print(a)
...: inner()
...: print(a)
...:
In [18]: outer()
5
6
5
In [19]: print(a)
4
作用域其實也可以從另一個角度理解,即我們在某個環(huán)境(environment)中,在確定一個name binding 值的時候,會從最內(nèi)層作用域順著往外找,找到的第一個該名字 binding 的對應(yīng)的值即為該 name 引用到的值。
需要強調(diào)的時候,函數(shù)的嵌套定義會引起定義域的嵌套,或者說環(huán)境擴展(內(nèi)層擴展外層)關(guān)系。類的定義又稍有不同,class 定義會引入新的 namespace(命名空間),命名空間和作用域是常拿來對比的概念,但這里按下不表,感興趣的可以自己去查查資料。
說到這里,要提一下,一個常被說起的反直覺例子:
In [50]: a = 4
In [51]: def test():
...: print(a) # 這里應(yīng)該輸出什么?
...: a = 5
...:
In [52]: test()
---------------------------------------------------------------------------
UnboundLocalError
Traceback (most recent call last)
in ()
----> 1 test()
in test()
1 def test():
----> 2 print(a)
3 a = 5
4
UnboundLocalError: local variable 'a' referenced before assignment
想象中,上面 print
處應(yīng)該輸出 4 或者 5 才對,為什么會報錯呢?這是因為 test
函數(shù)在被解釋器解析的時候,分詞器會掃一遍 test 函數(shù)定義中的所有 token(符號),看到賦值語句 a=5
的存在,就會明確 a
是一個局部變量,因此不會輸出 4。而在執(zhí)行到 print(a)
的時候,在局部環(huán)境中,a
還未被binding,因此會報 UnboundLocalError
。
稍微擴展說明一下,雖然 Python 是解釋執(zhí)行的,即輸入一句,解釋一句,執(zhí)行一句。但是對于代碼塊(即頭部語句,冒號與其關(guān)聯(lián)的縮進塊所構(gòu)成的復(fù)合語句(compound sentences),常見的有函數(shù)定義,類定義,循環(huán)語句等等)來說,還是會整體先掃一遍的。
一般來說,組成編程語言的元素,如變量、函數(shù)和類,會被設(shè)定不同的限制,而具有最少限制的元素,被我們稱為該編程語言中的一等公民。而一等公民最常見的特權(quán)有:
套用到 Python 中的函數(shù),即一個函數(shù)可以被賦值給某個變量,可以被其他函數(shù)接收和返回,可以定義在其他函數(shù)中(即嵌套定義):
In [32]: def test():
...: print('hello world')
...:
In [33]: t = test # 賦值給變量
In [34]: t()
hello world
In [35]: def wrapper(func):
...: print('wrapper')
...: func()
...:
In [36]: wrapper(t) # 作為參數(shù)傳遞
wrapper
hello world
In [37]: def add_num(a):
...: def add(b): # 嵌套定義
...: return a + b
...: return add # 作為函數(shù)的返回值
...:
...:
In [38]: add5 = add_num(5)
In [39]: add5(4)
Out[39]: 9
并不是在所有語言中,函數(shù)都是一等公民,比如 Java8 以前的 Java,上面四項權(quán)利 Java7 中的函數(shù)后幾項都沒有。使用函數(shù)作為第一等公民的做法,我們成為函數(shù)式編程。在這個大數(shù)據(jù)時代,由于對并發(fā)的友好性,傳統(tǒng)過程式語言(比如 Cpp、Java)都在新版本上逐漸支持函數(shù)式編程范式。
在這里,能夠操作其他函數(shù)的函數(shù)(即以其他函數(shù)作為參數(shù)或者返回值的函數(shù)),叫做高階函數(shù)。高階函數(shù)使得語言的表達能力大大增強,但同時,也增加了編程復(fù)雜度。
每個函數(shù)調(diào)用,會在環(huán)境中產(chǎn)生一個 frame(棧幀),并且在棧幀中會進行一些綁定,然后壓入函數(shù)調(diào)用棧中。在函數(shù)調(diào)用結(jié)束時,棧幀會被彈出,其中所進行的綁定也被解除,即垃圾回收,對應(yīng)的局部作用域也隨之消亡。
In [47]: def test():
...: x = 4
...: print(x)
...:
In [48]: test()
4
In [49]: x
---------------------------------------------------------------------------
NameError
Traceback (most recent call last)
in ()
----> 1 x
NameError: name 'x' is not defined
即在調(diào)用結(jié)束后,局部定義的變量 x
在外邊是訪問不到的。但是如之前例子中,返回的 add
函數(shù)卻引用了已經(jīng)調(diào)用結(jié)束的 add_num
中的變量 a
,怎么解釋這種現(xiàn)象呢?可以記住一條,也是之前提到過的:
函數(shù)嵌套定義時,內(nèi)部定義的函數(shù)所在的環(huán)境會自動擴展其定義所在環(huán)境
因此在外部函數(shù)返回后,返回的內(nèi)部函數(shù)依然維持了其定義時的擴展環(huán)境,也可以理解為由于內(nèi)部函數(shù)引用的存在,外部函數(shù)的環(huán)境中所有的綁定并沒有被回收。
千呼萬喚始出來,以為是高潮,其實已結(jié)束。
閉包就是建立在前面的這些概念上的,上面提到的某個例子:
In [37]: def add_num(a):
...: def add(b): # 嵌套定義
...: return a + b
...: return add # 作為函數(shù)的返回值
...:
...:
In [38]: add5 = add_num(5)
In [39]: add5(4)
Out[39]: 9
其實就是閉包。撿起之前伏筆,給出我對閉包的一個理解:它是一種高階函數(shù),并且外層函數(shù)(例子中的add_num
)將其內(nèi)部定義的函數(shù)(add
)作為返回值返回,同時由于返回的內(nèi)層函數(shù)擴展了外層函數(shù)的環(huán)境,也就是對其產(chǎn)生了一個引用,那么在調(diào)用返回的內(nèi)部函數(shù)(add5
)的時候,能夠引用到其(add
)定義時的外部環(huán)境(在例子中,即 a
的值)。
說了這么多,其實只是在邏輯層面或者說抽象層面去解釋閉包是什么,常跟哪些概念糾纏在一起。但這些都沒有真正觸到其本質(zhì),或者說依然是空中樓閣,如果想要真正理解閉包,可以去詳細了解下 Python 的解釋執(zhí)行機制,當(dāng)然,那就是編譯原理的范疇了。
“Python 中閉包概念是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!