Python中的閉包是什麼?
來自專欄 我與Python
目錄
- 作用域(Scope)
- 閉包(Closure)的定義
- 閉包的作用
- 總結(查詢回顧的朋友看這裡)
-------------------------------------------------------------------
2018.4.15
更新了對於函數作用域的理解內容
1.作用域
作用域限定了一個變數在程序中的有效範圍,對於一個函數,其內部定義的變數的作用域就是這個函數體內部,一旦函數結束被調用,此變數將被析構。簡言之,函數中定義的變數稱作局部變數,它只能在函數內部被引用。
下面的例子說明了局部變數不能在函數外訪問:
In [9]: def f1(): ...: a = 1 ...:In [10]: print(a)---------------------------------------------------------------------------NameError Traceback (most recent call last)<ipython-input-10-bca0e2660b9f> in <module>()----> 1 print(a)NameError: name a is not defined
而在所有函數之外定義的變數可以被函數訪問:
In [12]: b = 1In [13]: def f2(): ...: print(b) ...:In [14]: f2()1
- 這種內函數可以引用外部函數的變數,而反之不行的現象也可以這樣理解:函數內部碰到一個變數的時候函數會優先在自己的作用域裡面去尋找。
另外,由於Python支持函數的嵌套,按照函數的作用域原則,內函數可以訪問外函數定義的變數:
In [37]: def f1(): ...: x = 1 ...: def f2(): ...: print(x) ...: f2() ...:In [38]: f1()1
2.閉包的定義
我們將上一個代碼稍作修改:
In [50]: def f1(): ...: x = 1 ...: def f2(): ...: print(x) ...: return f2 ...:In [51]: m = f1()In [52]: n = f1()In [53]: m == nOut[53]: FalseIn [54]: m()Out [54]: 1
- 從上面的例子可以看出,
f1
每次返回的函數都不同。 - 還有一個隱含特性,在
In [54]: x()
這條代碼中,儘管隨著函數f1()
被調用,參數x
的作用域已經結束,然而函數m
仍然可以正常的引用參數x
,這表明了內嵌函數可以引用外部函數的參數,並且當這些參數的作用域隨著外部函數的調用結束後仍然能被內嵌函數引用。
通過上述的例子,我們再引入Wiki百科的對於閉包的定義:
在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變數的函數。這個被引用的自由變數將和這個函數一同存在,即使已經離開了創造它的環境也不例外。另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。
如果要在內嵌函數中修改引用的外部變數,需要在內嵌函數中對要修改的變數使用nonlocal
關鍵字進行聲明:
In [58]: def creatCounter(): # 實現一個加法器 每次調用內嵌函數就加一 ...: i = 0 ...: def count(): ...: nonlocal i # nonlocal關鍵字標誌著內嵌函數將修改外部變數 如果沒有這行代碼將會出錯 ...: i += 1 ...: return i ...: return count ...:In [59]: f = creatCounter()In [60]: f()Out[60]: 1In [61]: f()Out[61]: 2In [62]: f()Out[62]: 3
3.閉包的作用
閉包避免使用了全局變數,閉包允許將某些數據和函數關聯起來,這一點很像類。在面向對象過程中,我們將定義了一些屬性,並將它們與一些方法關聯起來。如果要定義只用一個方法的類,可以採用閉包的方法實現。另外,閉包在裝飾器中也很重要。
判斷一個函數是不是閉包,可以查看它的__closure__
屬性,如果是閉包,查看該屬性將會返回一個cell
對象組成的元組,分別對每個cell
對象查看其cell_contents
屬性,返回的內容就是閉包引用的自由變數的值。下面通過一個例子展示:
In [41]: def add(x,y): ...: def f(z): ...: return x+y+z ...: return f ...:In [42]: d = add(5,5)In [43]: d(9)Out[43]: 19In [44]: d(1)Out[44]: 11In [45]: d.__closure__ # 閉包的__closure__屬性Out[45]:(<cell at 0x000001F9A295CCD8: int object at 0x000000006F666140>, <cell at 0x000001F9A295C9A8: int object at 0x000000006F666140>)In [46]: for i in d.__closure__: ...: print(i.cell_contents) # 查看每個cell對象的內容 —— cell_contents屬性 ...:55
cell_contents
解釋了局部變數在脫離函數後仍然可以在函數之外被訪問,因為變數被存儲在cell_contents
中了。
4.總結
- 在python的作用域規則裡面,創建變數一定會在當前作用域里創建一個變數,但是訪問或者修改變數時會先在當前作用域查找變數,沒有找到匹配變數的話會依次向上在閉合的作用域裡面進行查找。
- 在內嵌函數中使用
nonlocal
關鍵字對外部變數進行聲明,可以允許內嵌函數修改外部變數。 - 閉包是引用了自由變數的函數。
- 閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。
- 閉包中包含了內部函數的代碼,以及所需外部函數中的變數的引用。其中所引用的變數稱作上值(upvalue)。
- 判斷一個函數是不是閉包,可以查看它是否存在
__closure__
屬性 __closure__
屬性是一個包含若干個cell
對象的元組,每個cell
對象的cell_content
屬性分別代表一個自由變數的值。
結語:這篇文章更多的只是作為學習筆記,其中參考了網上一些前輩的文章或是其他資料,所以有些可能會大量引用,如有冒犯,請私信我,還望諒解。
推薦閱讀: