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