淺顯理解 Python 閉包
2013-11-12 11:00
閉包這個概念在 JavaScript 中討論和使用得比較多,不過在 Python 中卻不是那麼顯而易見,之所以說“不是那麼”,是因為即使用到了,也沒用注意到而已,比如定義一個 Decorator 時,就已經用到閉包了。網上對閉包的各種解釋,感覺非常晦澀,在這裡談談我的淺顯認識:要形成閉包,首先得有一個巢狀的函式,即函式中定義了另一個函式,閉包則是一個集合,它包括了外部函式的區域性變數,這些區域性變數在外部函式返回後也繼續存在,並能被內部函式引用。
舉個例子
這是個經常使用到的例子,定義一個函式 generate_power_func
,它返回另一個函式,現在閉包形成的條件已經達到。
1 2 3 4 5 6 |
def generate_power_func(n): print "id(n): %X" % id(n) def nth_power(x): return x**n print "id(nth_power): %X" % id(nth_power) return nth_power |
對於內部函式 nth_power
,它能引用到外部函式的區域性變數 n
,而且即使 generate_power_func
已經返回。把這種現象就稱為閉包。具體使用一下。
1 2 3 4 5 |
>>> |
從結果可以看出,當 generate_power_func(4)
執行後,
建立和返回了 nth_power
這個函式物件,記憶體地址是
0x2C090C8,並且發現 raised_to_4
和它的記憶體地址相同,即 raised_to_4
只是這個函式物件的一個引用。先在全域性名稱空間中刪除 generate_power_func
1 2 3 |
>>> del generate_power_func >>> raised_to_4(2) 16 |
啊哈,居然沒出現錯誤, nth_power
是怎麼知道 n
的值是
4,而且現在 generate_power_func
甚至都不在這個名稱空間了。對,這就是閉包的作用,外部函式的區域性變數可以被內部函式引用,即使外部函式已經返回了。
_closure_ 屬性和 cell 物件
現在知道閉包是怎麼一回事了,那就到看看閉包到底是怎麼回事的時候了。Python 中函式也是物件,所以函式也有很多屬性,和閉包相關的就是 __closure__
屬性。__closure__
屬性定義的是一個包含
cell 物件的元組,其中元組中的每一個 cell 物件用來儲存作用域中變數的值。
1 2 3 4 5 6 |
>>> raised_to_4.__closure__ (<cell at 0x2bf4ec0: int object at 0x246f770>,) >>> type(raised_to_4.__closure__[0]) <type 'cell'> >>> raised_to_4.__closure__[0].cell_contents 4 |
就如剛才所說,在 raised_to_4
的 __closure__
屬性中有外部函式變數 n
的引用,通過記憶體地址可以發現,引用的都是同一個 n
。如果沒用形成閉包,則 __closure__
屬性為 None
。對於
Python 具體是如何實現閉包的,可以檢視 Python閉包詳解,它通過分析
Python 位元組碼來講述閉包的實現。
最後總結
閉包特性有著非常多的作用,不過都是需要時才會不經意的用上,不要像使用設計模式一樣去硬套這些法則。這篇文章按照自己的理解翻譯至 Python Closures Explained,可能和原文有些不同之處,如有疑惑,請檢視原文。附上一些參考資料。
- Python閉包詳解:從位元組碼出發瞭解 Python 閉包的實現機制
- 理解Javascript的閉包: 從 Javascript 的閉包中瞭解一些閉包特性,可以和 Python 作下對比