Python 閉包

在提到閉包之前,我們需要對(duì)函數(shù)做一些梳理:
函數(shù)的局部參數(shù)是無(wú)法保存的,每次執(zhí)行函數(shù)都是將參數(shù)初始化并執(zhí)行

閉包可以使函數(shù)擁有自己的環(huán)境上下文,在其中保存執(zhí)行后的信息。使函數(shù)表現(xiàn)的像一個(gè)對(duì)象,豐富了函數(shù)的功能。

0、引言


我們現(xiàn)在要實(shí)現(xiàn)一個(gè)具有緩存功能的函數(shù),"如果后續(xù)傳入相同的參數(shù),則不再計(jì)算,直接返回結(jié)果"

如果是在對(duì)象中,這種緩存非常簡(jiǎn)單就能實(shí)現(xiàn)。但是在函數(shù)中則異常麻煩:

  • 1 幻想中的寫法, [實(shí)際上并不能起到作用]
import time


def cache_sum(a, b):
    """這是一個(gè)具有緩存功能的求和函數(shù)"""
    cache = {}

    key = str(a) + str(b)
    if key in cache:            # 1. 具有緩存結(jié)果,則直接返回
        return cache.get(key)
    else:
                                # 2. 計(jì)算
        time.sleep(1)
        ret = a + b
        cache[key] = ret        # 3. 將結(jié)果存儲(chǔ)進(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 = {}作為局部變量,每次都會(huì)被初始化,根本起不到作用 [ 兩次執(zhí)行都耗時(shí)一秒,說明緩存沒有起作用]。

    1. 改進(jìn)寫法
def cache_sum(a, b):
    """這是一個(gè)具有緩存功能的求和函數(shù)"""

    key = str(a) + str(b)
    if key in cache:  # 1. 這里的 cache 由使用者提供
        return cache.get(key)
    else:
        # 2. 計(jì)算
        time.sleep(1)
        ret = a + b
        cache[key] = ret  # 3. 將結(jié)果存儲(chǔ)進(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

[ 第二次沒有耗時(shí),說明緩存起作用了]
在這種寫法中,我們執(zhí)行cache_sum還需要提供 cache = {}這樣一個(gè)變量,這樣會(huì)引起很多問題:

  • 使用者會(huì)不會(huì)忘記提供 cache = {}?

  • 使用者會(huì)不會(huì)在外部修改 cache?
    這些都是無(wú)法預(yù)料的問題。顯然不能作為這個(gè)問題的解決方式

    1. 使用閉包完成
import time


def as_cache_sum():
    """返回一個(gè)具有緩存功能的函數(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. 計(jì)算
            time.sleep(1)
            ret = a + b
            cache[key] = ret  # 3. 將結(jié)果存儲(chǔ)進(jìn)緩存

    return cache_sum

第二種寫法相比,這里提供變量cache是指外部函數(shù)中完成的,而使用者是接觸不到這個(gè)變量的。

細(xì)細(xì)的品,你是不是有點(diǎn)明白閉包的這個(gè)


一、使用“閉包”

“閉包”的本質(zhì)是函數(shù)的嵌套定義,即在函數(shù)內(nèi)部再定義函數(shù)。

在下面這個(gè)函數(shù)中

 def make_averager():
        """返回一個(gè)計(jì)算平均值的函數(shù)"""
        series = []

        def averager(new_value):
            series.append(new_value)

            total = sum(series)
            return total / len(series)

        return averager

調(diào)用 make_averager 時(shí),返回一個(gè) averager 函數(shù)對(duì)象。每次調(diào)用 averager 時(shí),它會(huì) 把參數(shù)添加到系列值中,然后計(jì)算當(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對(duì)series的修改會(huì)被保存起來(lái)(伴隨著averager,直到其被銷毀)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。