在 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
。
>>> 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)比使用類更加簡潔。
閉包的概念很簡單,但實現(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ù),你可能認為返回結(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