深入理解 Python 中的閉包(Closure)
更新日期: 2024 年 11 月 18 日
本文為 python 作用域系列文,第一篇:
- 基礎:深入理解 Python 的作用域與 LEGB 原則
- 1 階進化:深入理解 Python 中的 global 與 nonlocal 關鍵字
- 2 階進化:深入理解 Python 中的閉包(Closure)
閉包是指內部函數,能夠記住其所在的外部函數中的變量,即使外部函數執行完畢、局部變量理應被銷毀時,這些變量依然能被內部函數訪問。
閉包的存在讓我們能實現一些巧妙的邏輯,常用於回調函數、裝飾器等場景。
接下來,我們將從代碼範例入手,逐步解析閉包的形成與運作原理。
閱讀本文前,建議具備相關概念:
基本閉包範例
以下是一個典型的 Python 閉包範例:
def hi():
a = 1 # 外部函數的局部變量
def hey():
print(a) # 訪問外部函數的局部變量
return hey # 將內部函數作為返回值
new_hey = hi() # 執行 hi 函數,獲得內部函數 hey 的引用
new_hey() # 調用內部函數 hey,輸出: 1
函數執行的流程
hi()
執行時- 在
hi()
的作用域內,變量a
被賦值為1
。 - 定義了內部函數
hey
,該函數訪問了外部函數的變量a
。 - 函數
hi
返回了內部函數hey
的引用。
- 在
- 執行完
hi()
後- 按理說,
hi()
的執行結束會銷毀其作用域內的局部變量a
。 - 但由於內部函數
hey
引用了變量a
,Python 建立了一個特殊的結構來保存a
的值,這就是「細胞物件」(Cell Object)。
- 按理說,
- 調用
new_hey()
時- 內部函數
hey
被執行,通過細胞物件引用到了變量a
的值1
,從而輸出了結果。
- 內部函數
細胞物件(Cell Object)的作用
細胞物件是閉包形成的關鍵。
當外部函數中的變量被內部函數引用時,Python 會自動建立細胞物件來保存這些變量的值,即使外部函數已結束,這些變量依然可以被內部函數訪問。
讓我們通過簡化的圖解,來展示細胞物件的關係:
閉包形成前
hi.a -------------> 1
hi.hey.a ---------> (直接指向 hi.a)
閉包形成後
當 hi()
執行完畢,局部變量 a
本應銷毀,但細胞物件將其保存了下來:
hi.a -------------> (消失)
hi.hey.a ---------> [Cell Object] -----> 1
3. 驗證細胞物件的存在
Python 提供了檢查細胞物件的方法:
def hi():
a = 1
def hey():
print(a)
return hey
new_hey = hi()
print(new_hey.__closure__) # 輸出: (<cell at 0x...: int object at 0x...>,)
__closure__
屬性__closure__
是內部函數的一個屬性,返回所有被捕捉的變量。- 每個捕捉的變量都存放在一個細胞物件中。
延伸與應用
多變量閉包
閉包可以捕捉多個變量:
def outer(x):
def inner(y):
return x + y # 捕捉外部變量 x
return inner
add_five = outer(5)
print(add_five(10)) # 輸出: 15
此例中,變量 x
被內部函數 inner
捕捉並保存在細胞物件中,內部函數執行時即可訪問。
注意事項:變量可變性
細胞物件中保存的變量是共享的,因此修改外部變量時需謹慎:
def counter():
count = 0
def increment():
nonlocal count # 修改外部變量
count += 1
return count
return increment
c = counter()
print(c()) # 輸出: 1
print(c()) # 輸出: 2
使用 nonlocal
關鍵字可在內部函數中修改外部變量,這在計數器、狀態追蹤等情境中非常實用。
總結
閉包是 Python 中非常強大的特性,它通過細胞物件實現了內部函數,對外部作用域變量的持久訪問。
理解閉包的運作方式,有助於開發者靈活處理,多層函數嵌套以及狀態保存的需求。
學會檢查 __closure__
屬性並合理使用閉包,將使你的 Python 編碼技能更上一層樓!