在提到閉包之前,我們需要對函數(shù)做一些梳理:
函數(shù)的局部參數(shù)是無法保存的,每次執(zhí)行函數(shù)都是將參數(shù)初始化并執(zhí)行
而閉包可以使函數(shù)擁有自己的環(huán)境上下文,在其中保存執(zhí)行后的信息。使函數(shù)表現(xiàn)的像一個對象,豐富了函數(shù)的功能。
0、引言
我們現(xiàn)在要實(shí)現(xiàn)一個具有緩存功能的函數(shù),"如果后續(xù)傳入相同的參數(shù),則不再計算,直接返回結(jié)果"。
如果是在對象中,這種緩存非常簡單就能實(shí)現(xiàn)。但是在函數(shù)中則異常麻煩:
- 1
幻想中的寫法, [實(shí)際上并不能起到作用]
import time
def cache_sum(a, b):
"""這是一個具有緩存功能的求和函數(shù)"""
cache = {}
key = str(a) + str(b)
if key in cache: # 1. 具有緩存結(jié)果,則直接返回
return cache.get(key)
else:
# 2. 計算
time.sleep(1)
ret = a + b
cache[key] = ret # 3. 將結(jié)果存儲進(jìn)緩存
使用cache_sum
if __name__ == '__main__':
print(datetime.datetime.now()) # 2020-05-04 09:13:13.239913
cache_sum(1, 5)
print(datetime.datetime.now()) # 2020-05-04 09:13:14.242900
cache_sum(1, 5)
print(datetime.datetime.now()) # 2020-05-04 09:13:15.246474
可以看出,在函數(shù)中cache = {}作為局部變量,每次都會被初始化,根本起不到作用 [ 兩次執(zhí)行都耗時一秒,說明緩存沒有起作用]。
改進(jìn)寫法
def cache_sum(a, b):
"""這是一個具有緩存功能的求和函數(shù)"""
key = str(a) + str(b)
if key in cache: # 1. 這里的 cache 由使用者提供
return cache.get(key)
else:
# 2. 計算
time.sleep(1)
ret = a + b
cache[key] = ret # 3. 將結(jié)果存儲進(jìn)緩存
使用 cache_sum
if __name__ == '__main__':
cache = {}
print(datetime.datetime.now()) # 2020-05-04 09:19:43.842472
cache_sum(1, 5)
print(datetime.datetime.now()) # 2020-05-04 09:19:44.846783
cache_sum(1, 5)
print(datetime.datetime.now()) # 2020-05-04 09:19:44.846864
[ 第二次沒有耗時,說明緩存起作用了]
在這種寫法中,我們執(zhí)行cache_sum還需要提供 cache = {}這樣一個變量,這樣會引起很多問題:
使用者會不會忘記提供 cache = {}?使用者會不會在外部修改 cache?
這些都是無法預(yù)料的問題。顯然不能作為這個問題的解決方式- 使用
閉包完成
- 使用
import time
def as_cache_sum():
"""返回一個具有緩存功能的函數(shù)
你應(yīng)該這樣使用它
cache_sum = as_cache_sum()
ret = cache_sum(1,4)
print(ret) # 5
"""
cache = {}
def cache_sum(a, b):
key = str(a) + str(b)
if key in cache: # 1. 具有緩存結(jié)果,則直接返回
return cache.get(key)
else:
# 2. 計算
time.sleep(1)
ret = a + b
cache[key] = ret # 3. 將結(jié)果存儲進(jìn)緩存
return cache_sum
與第二種寫法相比,這里提供變量cache是指外部函數(shù)中完成的,而使用者是接觸不到這個變量的。
細(xì)細(xì)的品,你是不是有點(diǎn)明白
閉包的這個閉了
一、使用“閉包”
“閉包”的本質(zhì)是函數(shù)的嵌套定義,即在函數(shù)內(nèi)部再定義函數(shù)。
在下面這個函數(shù)中
def make_averager():
"""返回一個計算平均值的函數(shù)"""
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
return averager
調(diào)用 make_averager 時,返回一個 averager 函數(shù)對象。每次調(diào)用 averager 時,它會 把參數(shù)添加到系列值中,然后計算當(dāng)前平均值。
averager = make_averager()
print(averager(10)) # 10
print(averager(12)) # 11
print(averager(14)) # 12
在上述函數(shù)中make_averager并不包含真正的執(zhí)行邏輯,它只做了兩件事情
為真正的執(zhí)行函數(shù)提供環(huán)境上下文返回執(zhí)行函數(shù)
averager函數(shù)才是真正執(zhí)行邏輯的地方,它使用了make_averager為它提供了series環(huán)境變量,averager對series的修改會被保存起來(伴隨著averager,直到其被銷毀)