我在博客中曾經介紹過兩篇關於函數的文章,第一篇是 關於 Python 函數是第一類對象,第二篇是關於 Lambda 函數,今天來講說 Python 閉包。html
什麼是閉包?閉包有什麼用?爲何要用閉包?今天咱們就帶着這3個問題來一步一步認識閉包。python
閉包和函數緊密聯繫在一塊兒,介紹閉包前有必要先介紹一些背景知識,諸如嵌套函數、變量的做用域等概念編程
做用域是程序運行時變量可被訪問的範圍,定義在函數內的變量是局部變量,局部變量的做用範圍只能是函數內部範圍內,它不能在函數外引用。閉包
定義在模塊最外層的變量是全局變量,它是全局範圍內可見的,固然在函數裏面也能夠讀取到全局變量的。例如: app
num = 10 # 全局做用域變量
def foo():
print(num) # 10複製代碼
而在函數外部則不能夠訪問局部變量。例如:函數
def foo():
num = 10
print(num) # NameError: name 'num' is not defined複製代碼
函數不只能夠定義在模塊的最外層,還能夠定義在另一個函數的內部,像這種定義在函數裏面的函數稱之爲嵌套函數(nested function)例如:spa
def print_msg():
# print_msg 是外圍函數
msg = "zen of python"
def printer():
# printer是嵌套函數
print(msg)
printer()
# 輸出 zen of python
print_msg()複製代碼
對於嵌套函數,它能夠訪問到其外層做用域中聲明的非局部(non-local)變量,好比代碼示例中的變量 msg
能夠被嵌套函數 printer
正常訪問。.net
那麼有沒有一種可能即便脫離了函數自己的做用範圍,局部變量還能夠被訪問獲得呢?答案是閉包code
函數身爲第一類對象,它能夠做爲函數的返回值返回,如今咱們來考慮以下的例子:cdn
def print_msg():
# print_msg 是外圍函數
msg = "zen of python"
def printer():
# printer 是嵌套函數
print(msg)
return printer
another = print_msg()
# 輸出 zen of python
another()複製代碼
這段代碼和前面例子的效果徹底同樣,一樣輸出 "zen of python"。不一樣的地方在於內部函數 printer
直接做爲返回值返回了。
通常狀況下,函數中的局部變量僅在函數的執行期間可用,一旦 print_msg()
執行事後,咱們會認爲 msg
變量將再也不可用。然而,在這裏咱們發現 print_msg 執行完以後,在調用 another 的時候 msg 變量的值正常輸出了,這就是閉包的做用,閉包使得局部變量在函數外被訪問成爲可能。
看完這個例子,咱們再來定義閉包,維基百科上的解釋是:
在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即便已經離開了創造它的環境也不例外。因此,有另外一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。
這裏的 another
就是一個閉包,閉包本質上是一個函數,它有兩部分組成,printer
函數和變量 msg
。閉包使得這些變量的值始終保存在內存中。
閉包,顧名思義,就是一個封閉的包裹,裏面包裹着自由變量,就像在類裏面定義的屬性值同樣,自由變量的可見範圍隨同包裹,哪裏能夠訪問到這個包裹,哪裏就能夠訪問到這個自由變量。
閉包避免了使用全局變量,此外,閉包容許將函數與其所操做的某些數據(環境)關連起來。這一點與面向對象編程是很是相似的,在面對象編程中,對象容許咱們將某些數據(對象的屬性)與一個或者多個方法相關聯。
通常來講,當對象中只有一個方法時,這時使用閉包是更好的選擇。來看一個例子:
def adder(x):
def wrapper(y):
return x + y
return wrapper
adder5 = adder(5)
# 輸出 15
adder5(10)
# 輸出 11
adder5(6)複製代碼
這比用類來實現更優雅,此外裝飾器也是基於閉包的一中應用場景。
全部函數都有一個 __closure__
屬性,若是這個函數是一個閉包的話,那麼它返回的是一個由 cell 對象 組成的元組對象。cell 對象的cell_contents 屬性就是閉包中的自由變量。
>>> adder.__closure__
>>> adder5.__closure__
(<cell at 0x103075910: int object at 0x7fd251604518>,)
>>> adder5.__closure__[0].cell_contents
5複製代碼
這解釋了爲何局部變量脫離函數以後,還能夠在函數以外被訪問的緣由的,由於它存儲在了閉包的 cell_contents中了。
同步發表博客:foofish.net/python-clos…
公衆號:Python之禪 (id:VTtalk),分享 Python 等技術乾貨