Python v3.7.0
在函數嵌套的程序結構中,如果內層函數包含對外層函數局部變量的引用,同時外層函數的返回結果又是對內層函數的引用,這就構成了一個閉包。當外層函數在調用結束時,發現自己的局部變量在內層函數中有引用,就會把這個局部變量綁定到內部函數,然後自己再結束。
以一個實現可變參數求和的函數爲例,函數體定義如下:
def calc_sum(*args):
ax = 0
for n in args:
ax += n
return ax
但是,如果不需要立刻返回求和結構,而是在後面的代碼中,根據需要再計算怎麼辦?此時可以不返回求和的結果,而是返回求和的函數,修改後的代碼如下:
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax += n
return ax
return sum
s = lazy_sum(1, 2, 3, 4, 5)
print(s, type(s),s(),sep='\n')
# Output>>>
<function lazy_sum.<locals>.sum at 0x10768ed90>
<class 'function'>
15
在上面的代碼中,我們在外層函數lazy_sum
中又嵌套了的內層函數sum
,sum
引用了lazy_sum
的參數,並且lazy_sum
將sum
作爲返回結果。如果是按照命令式語言的規則(如C++,C#),在執行sum
函數時,會由於在其作用域內找不到args
變量而出錯,但是在函數式語言中,當內嵌的函數體內有引用外部作用域的變量時,將會把定義時涉及到的引用環境和函數體打包成一個整體返回,這中程序結構就是上面說的"閉包(Closure)"。所以說,閉包就是由函數及其相關的引用環境組合而成的實體,即:閉包=函數+引用環境。
從運行結果中可以看到,當調用lazy_sum(*args)
時,返回的是內部求和函數的引用地址,當調用函數sum()
時,才真正計算求和的結果。
進一步理解Python中的閉包概念,還有兩點需要特別注意,首先看下面的例子:
s1 = lazy_sum(1, 2, 3, 4, 5)
s2 = lazy_sum(1, 2, 3, 4, 5)
print(s1==s2)
# Output>>>
False
也就是說當我們調用lazy_sum()
時,即使傳入參數相同,每次調用也都會返回一個新的函數,彼此不會互相影響。
第二點,在閉包中修改外部作用域的局部變量時,需要使用關鍵字nonlocal
,否則會報錯。稍微修改一下上面求和的代碼,如下:
# ax = 0 # UnboundLocalError
def lazy_sum(*args):
ax = 0 # UnboundLocalError
def sum():
# ax = 0 # 正常
for n in args:
ax += n
return ax
return sum
s = lazy_sum(1, 2, 3, 4, 5)
print(s, type(s),s(),sep='\n')
# Output>>>
UnboundLocalError: local variable 'ax' referenced before assignment
我們嘗試將原本定義在內層函數中的變量 ax
放到了外層函數或函數體外部中進行定義,此時運行代碼都會拋出UnboundLocalError
的異常,這再看下面的代碼:
def decorator():
name = "Rethink"
def wrapper():
print(name)
return wrapper
deco()()
# Output>>>
Rethink
這裏看到,代碼可以正常運行,這是因爲在閉包中只是引用了外部作用域的局部變量,而沒有修改它的值。
在閉包結構中,內層函數改變外層函數的局部變量需要用nonlocal
關鍵字, nonlocal
不能定義新的外層函數變量,只能改變已有的外層函數變量,同時也不能改變全局變量,在看下面的例子:
# ax = 0 # no binding for nonlocal 'ax' found
def lazy_sum(*args):
ax = 0
def sum():
nonlocal ax
for n in args:
ax += n
return ax
return sum
s = lazy_sum(1, 2, 3, 4, 5)
print(s, type(s), s(), sep='\n')
# Output>>>
<function lazy_sum.<locals>.sum at 0x000001B0D80612F0>
<class 'function'>
15
從運行結果中可以看到,在閉包函數中使用 nonlocal
聲明外層函數中定義的ax
變量後,程序可以正常運行,但是如果ax
是定義在函數體外部的全局變量,則運行函數時,會報錯:no binding for nonlocal 'ax' found
.
[To be continued…]
參考文檔
-
Python基礎|深入閉包與變量作用域,公衆號:編程時光
-
Python與算法社區,Emily