10--Python 閉包與裝飾器

@Author : Roger TX (425144880@qq.com)
@Link : https://github.com/paotong999

一赎败、Python 閉包

1潜慎、函數(shù)作為返回值

高階函數(shù)除了可以接受函數(shù)作為參數(shù)外,還可以把函數(shù)作為結(jié)果值返回子姜。
我們來實(shí)現(xiàn)一個(gè)可變參數(shù)的求和洗做。通常情況下,求和的函數(shù)是這樣定義的:

def calc_sum(*args):
    ax = 0
    for n in args:
        ax = ax + n
    return ax

但是址芯,如果不需要立刻求和灾茁,而是在后面的代碼中,根據(jù)需要再計(jì)算怎么辦谷炸?可以不返回求和的結(jié)果北专,而是返回求和的函數(shù):

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

當(dāng)我們調(diào)用 lazy_sum() 時(shí),返回的并不是求和結(jié)果旬陡,而是求和函數(shù)拓颓。

在這里,我們?cè)诤瘮?shù) lazy_sum中又定義了函數(shù) sum描孟,并且驶睦,內(nèi)部函數(shù) sum 可以引用外部函數(shù) lazy_sum 的參數(shù)和局部變量砰左,當(dāng) lazy_sum 返回函數(shù) sum 時(shí),相關(guān)參數(shù)和變量都保存在返回的函數(shù)中场航,這種稱為 閉包(Closure) 的程序結(jié)構(gòu)擁有極大的威力缠导。

2、閉包

在一個(gè)外函數(shù)中定義了一個(gè)內(nèi)函數(shù)溉痢,內(nèi)函數(shù)里運(yùn)用了外函數(shù)的臨時(shí)變量僻造,并且外函數(shù)的返回值是內(nèi)函數(shù)的引用。

一般情況下孩饼,在我們認(rèn)知當(dāng)中髓削,如果一個(gè)函數(shù)結(jié)束,函數(shù)的內(nèi)部所有東西都會(huì)釋放掉镀娶,還給內(nèi)存立膛,局部變量都會(huì)消失。但是閉包是一種特殊情況汽畴,如果外函數(shù)在結(jié)束的時(shí)候發(fā)現(xiàn)有自己的臨時(shí)變量將來會(huì)在內(nèi)部函數(shù)中用到旧巾,就把這個(gè)臨時(shí)變量綁定給了內(nèi)部函數(shù),然后自己再結(jié)束忍些。

閉包是由函數(shù)及其相關(guān)的引用環(huán)境組合而成的實(shí)體(即:閉包=函數(shù)+引用環(huán)境)

函數(shù):

  • 函數(shù)只是一段可執(zhí)行代碼鲁猩,編譯后“固化”,每個(gè)函數(shù)在內(nèi)存中只有一份實(shí)例罢坝,得到函數(shù)的入口點(diǎn)便可以執(zhí)行函數(shù)廓握。

如果函數(shù)名后緊跟一對(duì)括號(hào),相當(dāng)于現(xiàn)在我就要調(diào)用這個(gè)函數(shù)
如果函數(shù)名后不跟括號(hào)嘁酿,相當(dāng)于只是一個(gè)函數(shù)的名字隙券,里面存了函數(shù)所在位置的引用

  • 在函數(shù)式編程語言中,函數(shù)是一等公民闹司,函數(shù)可以作為另一個(gè)函數(shù)的參數(shù)或返回值娱仔,可以賦給一個(gè)變量。

(First class value:第一類對(duì)象游桩,我們不需要像命令式語言中那樣借助函數(shù)指針牲迫,委托操作函數(shù))

  • 函數(shù)可以嵌套定義,即在一個(gè)函數(shù)內(nèi)部可以定義另一個(gè)函數(shù)借卧,有了嵌套函數(shù)這種結(jié)構(gòu)盹憎,便會(huì)產(chǎn)生閉包問題。

引用環(huán)境:

  • 當(dāng)內(nèi)嵌函數(shù)體內(nèi)引用到體外的變量時(shí)铐刘,將會(huì)把定義時(shí)涉及到的引用環(huán)境和函數(shù)體打包成一個(gè)整體返回

注意事項(xiàng):

  • 閉包中無法修改外部作用域的局部變量
def foo():
    m = 0
    def foo1(): 
        m = 1
        print (m)    # m = 1

    print (m)    # m = 0
    foo1()
    print (m)    # m = 0
 foo()
  • 在閉包中陪每,所有在賦值語句左面的變量都是局部變量
def foo():
    a = 1
    def bar():
        a = a + 1
        return a
    return bar

運(yùn)行時(shí),會(huì)報(bào)錯(cuò) UnboundLocalError: local variable 'a' referenced before assignment,變量a在賦值語句左邊被認(rèn)為是局部變量檩禾,會(huì)在 bar() 中去找在賦值語句右面的a的值挂签,而找到上層的變量a是不允許在局部內(nèi)被修改

  • 修改賦值和返回,將a改為b
  • 將a設(shè)定為一個(gè)容器盼产,a = [1]竹握,a[0] = a[0] + 1
  • a = a + 1 之前,使用語句nonloacal a就可以指定a不是閉包的局部變量辆飘,需要向上一層變量空間找這個(gè)變量。
  • 返回閉包時(shí)谓传,返回函數(shù)不要引用任何循環(huán)變量蜈项,或者后續(xù)會(huì)發(fā)生變化的變量
def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()    # 調(diào)用f1(),f2()和f3()結(jié)果應(yīng)該是1续挟,4紧卒,9,但實(shí)際結(jié)果是9诗祸,9跑芳,9

閉包的應(yīng)用:

  • 坐標(biāo)連續(xù)移動(dòng)
origin = [0, 0]  # 坐標(biāo)系統(tǒng)原點(diǎn)
legal_x = [0, 50]  # x軸方向的合法坐標(biāo)
legal_y = [0, 50]  # y軸方向的合法坐標(biāo)
def create(pos=origin):
    def player(direction,step):
        # 這里應(yīng)該首先判斷參數(shù)direction,step的合法性,比如direction不能斜著走直颅,step不能為負(fù)等
        # 然后還要對(duì)新生成的x博个,y坐標(biāo)的合法性進(jìn)行判斷處理,這里主要是想介紹閉包功偿,就不詳細(xì)寫了
        new_x = pos[0] + direction[0]*step
        new_y = pos[1] + direction[1]*step
        pos[0] = new_x
        pos[1] = new_y
        #注意盆佣!此處不能寫成 pos = [new_x, new_y],原因在上文有說過
        return pos
    return player

player = create()  # 創(chuàng)建棋子player械荷,起點(diǎn)為原點(diǎn)
print (player([1,0],10))  # 向x軸正方向移動(dòng)10步共耍,結(jié)果為[10, 0]
print (player([0,1],20))  # 向y軸正方向移動(dòng)20步,結(jié)果為[10, 20]
print (player([-1,0],10))  # 向x軸負(fù)方向移動(dòng)10步吨瞎,結(jié)果為[0, 20]
  • 取得文件"result.txt"中含有"pass"關(guān)鍵字的行
def make_filter(keep):  
    def the_filter(file_name):  
        file = open(file_name)  
        lines = file.readlines()  
        file.close()  
        filter_doc = [i for i in lines if keep in i]  
        return filter_doc  
    return the_filter 

filter = make_filter("yield")  
filter_result = filter("example.py")  
print(filter_result)
  • 裝飾器
    python中的使用@語法實(shí)現(xiàn)的單例模式就是利用閉包實(shí)現(xiàn)的痹兜,只不過用了@作為語法糖,使寫法更簡潔颤诀,閉包函數(shù)將函數(shù)的唯一實(shí)例保存在它內(nèi)部的 __closure__ 屬性中字旭,在再次創(chuàng)建函數(shù)實(shí)例時(shí),閉包檢查該函數(shù)實(shí)例已存在自己的屬性中着绊,不會(huì)再讓他創(chuàng)建新的實(shí)例谐算,而是將現(xiàn)有的實(shí)例返給它。

__closure__ 屬性返回的是一個(gè)元組對(duì)象归露,包含了閉包引用的外部變量洲脂。

  • 若主函數(shù)內(nèi)的閉包不引用外部變量,就不存在閉包,主函數(shù)的 _closure__ 屬性永遠(yuǎn)為None
  • 若主函數(shù)沒有return子函數(shù)恐锦,就不存在閉包往果,主函數(shù)不存在 _closure__ 屬性,拋出異常

二一铅、Python 裝飾器

裝飾器分為:不帶參數(shù)的裝飾器 和 帶參數(shù)的裝飾器
本質(zhì)上陕贮,decorator就是一個(gè)返回函數(shù)的高階函數(shù),實(shí)現(xiàn)了以下兩步

  • 將被修飾的函數(shù)(函數(shù) B)作為參數(shù)傳給 @ 符號(hào)引用的函數(shù)(函數(shù) A)
  • 將函數(shù) B 替換(裝飾)成第 1 步的返回值

1潘飘、不帶參數(shù)的裝飾器

@a_decorator
def f(...):

經(jīng)過a_decorator后肮之, 函數(shù)f就相當(dāng)于以f為參數(shù)調(diào)用a_decorator返回結(jié)果

f = a_decorator(f)

def makeBold(fn):
    print("makeBold"*2)
    def wrapped1():   #注意為了演示結(jié)果這里講wrapped函數(shù),分為wrapped1,wrapped2
        print("makeBold"*3)
        print("asd" + fn() + "asd")
    return wrapped1
 
def makeItalic(fn):
    print("makeItalic"*2)
    def wrapped2():     #注意為了演示結(jié)果這里講wrapped函數(shù)卜录,分為wrapped1,wrapped2
        print("makeItalic" *3)
        return "zzz" + fn() + "zzz"
    return wrapped2
 
#使用兩個(gè)裝飾器同時(shí)裝飾一個(gè)函數(shù)戈擒,可以三個(gè),甚至多個(gè)艰毒。原理一樣
@makeBold  #其效果等同于執(zhí)行test_B_I=makeBold(makeItalic(test_B_I))
@makeItalic  #其效果等同于執(zhí)行test_B_I=makeItalic(test_B_I)
def test_B_I():   
    print("test_B_I"*2)
    return "this is the test_B_I"

2筐高、帶參數(shù)的裝飾器

這里就給一個(gè)式子, 剩下的問題可以自己去想

@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):

這個(gè)式子相當(dāng)于

func = decomaker(argA, argB, ...)(func)

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('execute')
def now():
    print('2015-3-25')

和兩層嵌套的decorator相比丑瞧,3層嵌套的效果是這樣的:

now = log('execute')(now)

我們來剖析上面的語句柑土,首先執(zhí)行l(wèi)og('execute'),返回的是decorator函數(shù)绊汹,再調(diào)用返回的函數(shù)稽屏,參數(shù)是now函數(shù),返回值最終是wrapper函數(shù)

>>> now()
execute now():
2015-3-25
>>> print(now.__name__)
'wrapper'

函數(shù)也是對(duì)象灸促,它有 __name__ 等屬性诫欠,但你去看經(jīng)過decorator裝飾之后的函數(shù),它們的 __name__ 已經(jīng)從原來的 now 變成了 wrapper

  • 需要把原始函數(shù)的 __name__ 等屬性復(fù)制到 wrapper() 函數(shù)中浴栽,需要使用Python內(nèi)置的 functools.wraps 裝飾器
  • 需要導(dǎo)入 functools 模塊荒叼。然后在定義 wrapper() 的前面加上 @functools.wraps(func) 即可

3、裝飾器類

在Python中典鸡, 其實(shí)函數(shù)也是對(duì)象被廓。 反過來, 對(duì)象其實(shí)也可以像函數(shù)一樣調(diào)用萝玷,只要在類的方法中實(shí)現(xiàn) __call__ 方法嫁乘,就可以實(shí)現(xiàn)裝飾器類。

裝飾器要求接受一個(gè)callable對(duì)象球碉,并返回一個(gè)callable對(duì)象蜓斧。那么用類來實(shí)現(xiàn)也是也可以的。我們可以讓類的構(gòu)造函數(shù) __init__() 接受一個(gè)函數(shù)睁冬,然后重載 __call__()并返回一個(gè)函數(shù)挎春,也可以達(dá)到裝飾器函數(shù)的效果。

class logging(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print "function: {func}()".format(func=self.func.__name__)
        return self.func(*args, **kwargs)

@logging
def say(something):
    print "say {}!".format(something)

如果需要通過類形式實(shí)現(xiàn)帶參數(shù)的裝飾器,那么會(huì)比前面的例子稍微復(fù)雜一點(diǎn)直奋。那么在構(gòu)造函數(shù)里接受的就不是一個(gè)函數(shù)能庆,而是傳入的參數(shù)。通過類把這些參數(shù)保存起來脚线。然后在重載 __call__ 方法是就需要接受一個(gè)函數(shù)并返回一個(gè)函數(shù)搁胆。

class logging(object):
    def __init__(self, level='INFO'):
        self.level = level
        
    def __call__(self, func): # 接受函數(shù)
        def wrapper(*args, **kwargs):
            print "{level}: {func}()".format(level=self.level, func=func.__name__)
            func(*args, **kwargs)
        return wrapper  #返回函數(shù)

@logging(level='INFO')
def say(something):
    print "say {}!".format(something)

4、多個(gè)裝飾器

關(guān)于多個(gè)裝飾器修飾一個(gè)函數(shù)

  1. 當(dāng)一個(gè)函數(shù)被多個(gè)裝飾器裝飾時(shí)邮绿,裝飾器的加載順序是從內(nèi)到外的(從下往上的)渠旁。其實(shí)很好理解:裝飾器是給函數(shù)裝飾的,所以要從靠近函數(shù)的裝飾器開始從內(nèi)往外加載
  2. 外層的裝飾器船逮,是給里層裝飾器裝飾后的結(jié)果進(jìn)行裝飾一死。相當(dāng)于外層的裝飾器裝飾的函數(shù)是里層裝飾器的裝飾原函數(shù)后的結(jié)果函數(shù)(裝飾后的返回值函數(shù))。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末傻唾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子承耿,更是在濱河造成了極大的恐慌冠骄,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件加袋,死亡現(xiàn)場(chǎng)離奇詭異凛辣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)职烧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門扁誓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蚀之,你說我怎么就攤上這事蝗敢。” “怎么了足删?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵寿谴,是天一觀的道長。 經(jīng)常有香客問我失受,道長讶泰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任拂到,我火速辦了婚禮痪署,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兄旬。我一直安慰自己狼犯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辜王,像睡著了一般劈狐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呐馆,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天肥缔,我揣著相機(jī)與錄音,去河邊找鬼汹来。 笑死续膳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的收班。 我是一名探鬼主播坟岔,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼摔桦!你這毒婦竟也來了社付?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤邻耕,失蹤者是張志新(化名)和其女友劉穎鸥咖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兄世,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡啼辣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了御滩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸥拧。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖削解,靈堂內(nèi)的尸體忽然破棺而出富弦,到底是詐尸還是另有隱情,我是刑警寧澤氛驮,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布舆声,位于F島的核電站,受9級(jí)特大地震影響柳爽,放射性物質(zhì)發(fā)生泄漏媳握。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一磷脯、第九天 我趴在偏房一處隱蔽的房頂上張望蛾找。 院中可真熱鬧,春花似錦赵誓、人聲如沸打毛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽幻枉。三九已至碰声,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熬甫,已是汗流浹背胰挑。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留椿肩,地道東北人瞻颂。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像郑象,于是被迫代替她去往敵國和親贡这。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔厂榛,今天18年5月份再次想寫文章盖矫,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,732評(píng)論 2 9
  • 本文為《爬著學(xué)Python》系列第四篇文章。從本篇開始,本專欄在順序更新的基礎(chǔ)上,會(huì)有不規(guī)則的更新蹄衷。 在Pytho...
    SyPy閱讀 2,489評(píng)論 4 11
  • 閉包和裝飾器 1.8 閉包和裝飾器 學(xué)習(xí)目標(biāo) 1. 能夠說出閉包的定義形式 2. 能夠說出裝飾器的實(shí)現(xiàn)形式 ...
    Cestine閱讀 532評(píng)論 0 0
  • 部分細(xì)節(jié)自己改了點(diǎn),也加了點(diǎn)自己例子,基本上屬于轉(zhuǎn)載拷橘。轉(zhuǎn)載出處:https://my.oschina.net/le...
    洛克黃瓜閱讀 1,964評(píng)論 0 3
  • 1. 閉包 概念:在函數(shù)嵌套的前提下局义,內(nèi)層函數(shù)引用了外層函數(shù)的變量(包括參數(shù)),外層函數(shù)又把內(nèi)層函數(shù)當(dāng)做返回值進(jìn)行...
    馬本不想再等了閱讀 316評(píng)論 1 7