閉包c(diǎn)losure——沒有閉包,裝飾器的功能會大打折扣

Python的裝飾器是一大利器,幾乎所有的流行框架,都在廣泛使用裝飾器

為什么說沒有閉包( closure) ,裝飾器的功能會大打折扣?

一個(gè)最簡易的裝飾器是什么都不不做,只是透傳一個(gè)函數(shù)對象

def final(f):
      return f

裝飾器的原理是

@deco
def func():
      ....
func() # call

等價(jià)于

def func():
      ...
func = deco(func) # 注意這里
func() 

func = deco(func) 右邊的表達(dá)式有可能偷梁換柱把函數(shù)換成了另外一個(gè)函數(shù),有可能沒有換,只是在對func對象做了一些事情,比如加一點(diǎn)屬性之類的,也可以只是在func調(diào)用之前增加一些別的事情。

deco(func) 內(nèi)部做什么,靈活性極大
這也是裝飾器的特點(diǎn)之一——不需要修改func的定義和實(shí)現(xiàn),既可以靈活地操縱它的調(diào)用行為,借用動態(tài)語言的特性,既可以動態(tài)注入屬性,也可以添加想要的邏輯,很靈活。

更多的一種裝飾器是——偷梁換柱。
即經(jīng)過修飾之后函數(shù)已經(jīng)不是原來那個(gè)??赡茉谘b飾器器內(nèi)部已經(jīng)計(jì)算出結(jié)果,同時(shí)轉(zhuǎn)換成另外一個(gè)函數(shù)。

這個(gè)“另外”的函數(shù)通常是通過閉包來提供的。

def deco(func):
      def wrapper():
            ret = func()
            return ret
      return wrapper 

閉包在python中有一個(gè) __closure__ 協(xié)議

代碼

def advance_avg():
    ls = [] # 只初始化一次
    s = 1

    def wrapper(num):
        nonlocal s # 必須要聲明 nolocal 否則會報(bào)告未知屬性錯(cuò)誤
        s += num
        print(f"s is {s}")
        ls.append(num)
        total = sum(ls)
        return total / len(ls)
    return wrapper

當(dāng)調(diào)用一次 advance_avg() 的時(shí)候,s和ls都會綁定在對象上

adv = advance_avg() # int 和 list對象都已經(jīng)綁定

以后每次調(diào)用adv(val) 綁定的ls和s都可以被內(nèi)部的函數(shù)wrapper操作。而且一大特性是這兩個(gè)變量可以緩存上一次調(diào)用之后計(jì)算的結(jié)果,且看

adv = advance_avg()
adv(1) # 結(jié)果是1
adv(2) # 結(jié)果是1.5 不是2
adv.__closure__ # 輸出 <cell at 0x7f8da8143520: list object at 0x7f8d9801db40>, <cell at 0x7f8da8143910: int object at 0x108f92ae0>

adv的 __closure__ 屬性輸出了一個(gè)int 和 list對象地址,這兩個(gè)對象的生命周期跟隨adv 。

理解閉包關(guān)鍵的地方在于作用域的概念。

有個(gè)可能不太精確的類比
對于嵌套閉包,我們可以把最上層的函數(shù)定義視為一個(gè)類定義,在python 中一切皆對象,包括函數(shù)

對于類的寫法,我們同樣可以寫一個(gè)上述用起來一模一樣的功能類,只是需要在類內(nèi)部定義一下__call__ —— 大佬推薦的工程級別的裝飾器寫法,用定義了__call__ 函數(shù)的類

class Average:
    def __init__(self):
        self.series = []
        self.s = 0 

    def __call__(self, new_value):
        self.series.append(new_value)
        total = sum(self.series)
        return total / len(self.series)

這個(gè)類用起來跟上面的閉包函數(shù)形式上沒有太大的區(qū)別。
__init__ 函數(shù)有一次性聲明并且追隨Average對象聲明周期的兩個(gè)變量,這個(gè)和閉包一樣,帶閉包的 advance_avg函數(shù)對象保持著一個(gè)int和一個(gè)list的生命周期

總結(jié)

  • 類的方法,可以訪問類內(nèi)的self屬性
  • 方法中的方法(閉包)可以訪問上層方法定義的局部屬性(類比與類內(nèi)定義的self 方法)
  • 函數(shù)可以多層嵌套,訪問范圍是內(nèi)層有外層的訪問權(quán),但是反過來不行——否則封裝毫無意義
  • 閉包訪問上層局部變量,如果變量是不可變對象,一定要使用 nolocal聲明,否則會報(bào)告未知屬性錯(cuò)誤
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容