以前也發(fā)過介紹閉包的文章,今天學(xué)習(xí)一下如何在閉包中攜帶狀態(tài)。
作者:Ethan?
原文:https://funhacks.net/2016/11/17/closure/
閉包
在 Python 中,函數(shù)也是一個對象。因此,我們在定義函數(shù)時,可以再嵌套定義一個函數(shù),并將該嵌套函數(shù)返回,比如:
from math import pow
def make_pow(n):
? ?def inner_func(x): ? ? # 嵌套定義了 inner_func
? ? ? ?return pow(x, n) ? # 注意這里引用了外部函數(shù)的 n
? ?return inner_func ? ? ?# 返回 inner_func
上面的代碼中,函數(shù)?make_pow?里面又定義了一個內(nèi)部函數(shù)?inner_func?,然后將該函數(shù)返回。因此,我們可以使用?make_pow?來生成另一個函數(shù):
>> > pow2 = make_pow(2) ?# pow2 是一個函數(shù),參數(shù) 2 是一個自由變量
>> > pow2
<function inner_func at 0x10271faa0 >
>> > pow2(6)
36.0
我們還注意到,內(nèi)部函數(shù)?inner_func?引用了外部函數(shù)?make_pow?的自由變量?n?,這也就意味著,當(dāng)函數(shù)?make_pow?的生命周期結(jié)束之后,?n?這個變量依然會保存在?inner_func?中,它被?inner_func?所引用。
>> > del make_pow ? ? ? ? # 刪除 make_pow
>> > pow3 = make_pow(3)
Traceback(most recent call last):
? ?File "<stdin>", line 1, in < module >
NameError:
? ?name 'make_pow' is not defined
>> > pow2(9) ? ? # pow2 仍可正常調(diào)用,自由變量 2 仍保存在 pow2 中
81.0
---|---
像上面這種情況,一個函數(shù)返回了一個內(nèi)部函數(shù),該內(nèi)部函數(shù)引用了外部函數(shù)的相關(guān)參數(shù)和變量,我們把該返回的內(nèi)部函數(shù)稱為閉包( Closure )。
在上面的例子中,?inner_func?就是一個閉包,它引用了自由變量?n?。
閉包的作用
閉包的最大特點就是引用了自由變量,即使生成閉包的環(huán)境已經(jīng)釋放,閉包仍然存在;
閉包在運行時可以有多個實例,即使傳入的參數(shù)相同,比如:
>> > pow_a = make_pow(2)
>> > pow_b = make_pow(2)
>> > pow_a == pow_b
False
利用閉包,我們還可以模擬類的實例。
這里構(gòu)造一個類,用于求一個點到另一個點的距離:
from math import sqrt
class Point(object):
? ?def __init__(self, x, y):
? ? ? ?self.x, self.y = x, y
? ?def get_distance(self, u, v):
? ? ? ?distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)
? ? ? ?return distance
>> > pt = Point(7, 2) ? ? ? ?# 創(chuàng)建一個點
>> > pt.get_distance(10, 6) ?# 求到另一個點的距離
5.0
用閉包來實現(xiàn):
def point(x, y):
? ?def get_distance(u, v):
? ? ? ?return sqrt((x - u) ** 2 + (y - v) ** 2)
? ?return get_distance
>> > pt = point(7, 2)
>> > pt(10, 6)
5.0
可以看到,結(jié)果是一樣的,但使用閉包實現(xiàn)比使用類更加簡潔。
常見誤區(qū)
閉包的概念很簡單,但實現(xiàn)起來卻容易出現(xiàn)一些誤區(qū),比如下面的例子:
def count():
? ?funcs = []
? ?for i in [1, 2, 3]:
? ? ? ?def f():
? ? ? ? ? ?return i
? ? ? ?funcs.append(f)
? ?return funcs
在該例子中,我們在每次?for?循環(huán)中創(chuàng)建了一個函數(shù),并將它存到?funcs?中?,F(xiàn)在,調(diào)用上面的函數(shù),你可能認(rèn)為返回結(jié)果是 1, 2, 3,事實上卻不是:
>> > f1, f2, f3 = count()
>> > f1()
3
>> > f2()
3
>> > f3()
3
為什么呢?原因在于上面的函數(shù)?f?引用了變量?i?,但函數(shù)?f?并非立刻執(zhí)行,當(dāng)?for?循環(huán)結(jié)束時,此時變量?i?的值是3,?funcs?里面的函數(shù)引用的變量都是 3,最終結(jié)果也就全為 3。
因此,我們應(yīng)盡量避免在閉包中引用循環(huán)變量,或者后續(xù)會發(fā)生變化的變量。
那上面這種情況應(yīng)該怎么解決呢?我們可以再創(chuàng)建一個函數(shù),并將循環(huán)變量的值傳給該函數(shù),如下:
def count():
? ?funcs = []
? ?for i in [1, 2, 3]:
? ? ? ?def g(param):
? ? ? ? ? ?f = lambda: param ? ?# 這里創(chuàng)建了一個匿名函數(shù)
? ? ? ? ? ?return f
? ? ? ?funcs.append(g(i)) ? ? ? ?# 將循環(huán)變量的值傳給 g
? ?return funcs
>> > f1, f2, f3 = count()
>> > f1()
1
>> > f2()
2
>> > f3()
3
小結(jié)
閉包是攜帶自由變量的函數(shù),即使創(chuàng)建閉包的外部函數(shù)的生命周期結(jié)束了,閉包所引用的自由變量仍會存在。
閉包在運行可以有多個實例。
盡量不要在閉包中引用循環(huán)變量,或者后續(xù)會發(fā)生變化的變量。
參考資料
題圖:pexels,CC0 授權(quán)。
點擊閱讀原文,查看更多 Python 教程和資源。
閱讀原文:http://mp.weixin.qq.com/s?__biz=MzAwNDc0MTUxMw==&mid=2649639864&idx=1&sn=691fe0541a31045bd7f8d78969bafb16&scene=0#wechat_redirect