Python: 攜帶狀態的閉包
在 Python 中,函數也是一個對象。因此,我們在定義函數時,可以再嵌套定義一個函數,并將該嵌套函數返回,比如:
frommathimportpow
defmake_pow(n):
definner_func(x): # 嵌套定義了 inner_func
return pow(x, n) # 注意這里引用了外部函數的 n
return inner_func # 返回 inner_func
上面的代碼中,函數 make_pow 里面又定義了一個內部函數 inner_func ,然后將該函數返回。因此,我們可以使用 make_pow 來生成另一個函數:
>>> pow2 = make_pow(2) # pow2 是一個函數,參數 2 是一個自由變量
>>> pow2
<functioninner_funcat 0x10271faa0>
>>> pow2(6)
36.0
我們還注意到,內部函數 inner_func 引用了外部函數 make_pow 的自由變量 n ,這也就意味著,當函數 make_pow 的生命周期結束之后, n 這個變量依然會保存在 inner_func 中,它被 inner_func 所引用。
>>> del make_pow # 刪除 make_pow
>>> pow3 = make_pow(3)
Traceback (mostrecentcalllast):
File "<stdin>", line 1, in <module>
NameError: name 'make_pow' is not defined
>>> pow2(9) # pow2 仍可正常調用,自由變量 2 仍保存在 pow2 中
81.0
像上面這種情況,一個函數返回了一個內部函數,該內部函數引用了外部函數的相關參數和變量,我們把該返回的內部函數稱為 閉包(Closure) 。
在上面的例子中, inner_func 就是一個閉包,它引用了自由變量 n 。
閉包的作用
- 閉包的最大特點就是引用了自由變量,即使生成閉包的環境已經釋放,閉包仍然存在;
- 閉包在運行時可以有多個實例,即使傳入的參數相同,比如:
>>> pow_a = make_pow(2)
>>> pow_b = make_pow(2)
>>> pow_a == pow_b
False
- 利用閉包,我們還可以模擬類的實例。
這里構造一個類,用于求一個點到另一個點的距離:
frommathimportsqrt
class Point(object):
def__init__(self, x, y):
self.x, self.y = x, y
defget_distance(self, u, v):
distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)
return distance
>>> pt = Point(7, 2) # 創建一個點
>>> pt.get_distance(10, 6) # 求到另一個點的距離
5.0
用閉包來實現:
defpoint(x, y):
defget_distance(u, v):
return sqrt((x - u) ** 2 + (y - v) ** 2)
return get_distance
>>> pt = point(7, 2)
>>> pt(10, 6)
5.0
可以看到,結果是一樣的,但使用閉包實現比使用類更加簡潔。
常見誤區
閉包的概念很簡單,但實現起來卻容易出現一些誤區,比如下面的例子:
defcount():
funcs = []
for i in [1, 2, 3]:
def f():
return i
funcs.append(f)
return funcs
在該例子中,我們在每次 for 循環中創建了一個函數,并將它存到 funcs 中。現在,調用上面的函數,你可能認為返回結果是 1, 2, 3,事實上卻不是:
>>> f1, f2, f3 = count()
>>> f1()
3
>>> f2()
3
>>> f3()
3
為什么呢?原因在于上面的函數 f 引用了變量 i ,但函數 f 并非立刻執行,當 for 循環結束時,此時變量 i 的值是3, funcs 里面的函數引用的變量都是 3,最終結果也就全為 3。
因此,我們應 盡量避免在閉包中引用循環變量,或者后續會發生變化的變量 。
那上面這種情況應該怎么解決呢?我們可以再創建一個函數,并將循環變量的值傳給該函數,如下:
defcount():
funcs = []
for i in [1, 2, 3]:
def g(param):
f = lambda : param # 這里創建了一個匿名函數
return f
funcs.append(g(i)) # 將循環變量的值傳給 g
return funcs
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
2
>>> f3()
3
小結
- 閉包是攜帶自由變量的函數,即使創建閉包的外部函數的生命周期結束了,閉包所引用的自由變量仍會存在。
- 閉包在運行可以有多個實例。
- 盡量不要在閉包中引用循環變量,或者后續會發生變化的變量。
參考資料
來自:http://python.jobbole.com/86846/