python之理解閉包和裝飾器

python之理解閉包和裝飾器

1、閉包函數(shù)

1.1 python中函數(shù)都是對象

def shut(word='yes'):
    print(word.capitalize())


shut()
scream = shut
scream()
print(type(shut))
print(id(shut))
print(id(scream))

del shut
scream()
print(id(scream))

結(jié)果:

Yes
Yes
<class 'function'>
33454008
33454008
Yes
33454008

上面定義一個shut函數(shù)雌团,由type獲取其類型可知是class燃领,那它當(dāng)然是一個對象了(具體可參考理解元類)。

對象的話锦援,我們就知道可以賦值給另外一個變量猛蔽,可以在其它函數(shù)中定義,且函數(shù)可以返回另一個函數(shù)灵寺。

shut()這里是函數(shù)調(diào)用曼库,scream = shut函數(shù)引用,scream()調(diào)用略板,然后下面打印它們的內(nèi)存地址是一樣的凉泄,再下面刪除del shut變量,調(diào)用scream(),內(nèi)存地址不變蚯根,這里可以想到另外一個東西:python的垃圾回收機制后众,其它理解系列會學(xué)習(xí)下。

1.2 函數(shù)引用與閉包

這個嘮叨要一段時間颅拦,詳細了解引用js的介紹javascript之理解閉包蒂誉。

簡單來說,閉包是滿足下面幾個東西的函數(shù):

  • 嵌套函數(shù)距帅,即一個函數(shù)內(nèi)部定義另外一個函數(shù)右锨,外層函數(shù)稱為A,內(nèi)層函數(shù)稱為B碌秸。
  • 在嵌套函數(shù)中的內(nèi)層函數(shù)B在外層函數(shù)A中所在的全局作用域范圍被調(diào)用绍移。
  • 這時調(diào)用B函數(shù)的作用域有兩個:A所在的全局作用域和B所在A中的局部作用域。

再簡單來說就是:閉包就是能夠讀取其它函數(shù)內(nèi)部變量的函數(shù)讥电。這里可以理解B是閉包函數(shù)蹂窖,然后A是其它函數(shù)。

一個簡單例子:

def talk(name='shut'):
    who = 'daocoder'

    def shut(word='yes'):
        print('shut %s to %s' % (word.upper(), who))

    def whisper(word='yes'):
        print('whisper %s to %s' % (word.lower(), who))

    if name == 'shut':
        return shut
    elif name == 'whisper':
        return whisper
    else:
        return shut


print(talk())
talk()(word='mudai')
talk('whisper')(word='mudai')

結(jié)果:

<function talk.<locals>.shut at 0x0000000001F27840>
shut MUDAI to daocoder
whisper mudai to daocoder

talk函數(shù)內(nèi)部再定義shutwhisper函數(shù)恩敌,并且兩個函數(shù)用到了外邊函數(shù)的變量whoname瞬测,那么這兩個函數(shù)以及用到的一些變量稱之為閉包。

1.3 閉包啥作用呢

def line_conf(a, b):
    def line(x):
        return a * x + b
    return line


line1 = line_conf(2, 5)
line2 = line_conf(1, 3)

print(line1(2))
print(line2(2))

結(jié)果:

9
5

上面的例子中纠炮,函數(shù)line和變量a月趟、b就構(gòu)成了閉包。在創(chuàng)建閉包的時候恢口,我們通過line_conf(a, b)說明了變量的取值孝宗,那么這樣就確定了最終閉包函數(shù)(y=2x+5和y=x+3)。且我們只需要變換不同的參數(shù)就可以獲取不同的直線函數(shù)表達式耕肩。這里體現(xiàn)代碼的復(fù)用性因妇。另外可以因此隱藏內(nèi)部實現(xiàn)細節(jié)问潭。

注意:由于閉包引用了外部函數(shù)的局部變量,那么外部函數(shù)的局部變量就一直會存在于內(nèi)存中沙峻,內(nèi)存泄漏是一個隱患。

2两芳、裝飾器

提到裝飾器摔寨,應(yīng)該提及另一個東西AOP(Aspect Origented Programming)面向切換編程。它的作用很強大怖辆,聯(lián)想實際業(yè)務(wù)的場景是复,我們想用戶在開始訪問時記錄一個日志,即整個框架跑起來初始化的時候竖螃,解析請求路由之前做這些事情淑廊。另外可用的場景有插入日志、性能測試特咆、事務(wù)處理(登錄)等季惩。

裝飾器即基于AOP思想的一個實現(xiàn),也利用到了上節(jié)說的閉包腻格。有了裝飾器画拾,我們就可以抽離出大量與函數(shù)本身無關(guān)的代碼并重復(fù)加以利用,概況的說:裝飾器就是為已存在的對象添加額外的功能菜职。

2.1 python里的裝飾器

def makebold(fn):
    def wrapped():
        return '<b>' + fn() + '</b>'
    return wrapped


def makeitalic(fn):
    def wrapped():
        return '<i>' + fn() + '</i>'
    return wrapped


@makebold
@makeitalic
def say():
    return "Hello"


print(say())

結(jié)果:

<b><i>Hello</i></b>

效果就如上青抛,那繼續(xù)討論輪子是怎么造的。

2.2 手動實現(xiàn)裝飾器

def my_decorator(fn):
    def wrapped(*args, **kwargs):
        new_name = kwargs['name'].replace('daocoder', 'mudai')
        new_age = args[0] + 1
        return fn(new_age, name=new_name)
    return wrapped


def say_name(*args, **kwargs):
    return "my name is %s and %d years old" % (kwargs['name'], args[0])


print(say_name(26, name='daocoder'))
print(my_decorator(say_name)(26, name='daocoder'))

結(jié)果:

print(say_name(26, name='daocoder'))
print(my_decorator(say_name)(26, name='daocoder'))

看著有些別扭酬核、大體想法希望能表達出來蜜另。以上的調(diào)用過程如下:

1、say_name作為參數(shù)傳遞給my_decorator后嫡意,say_name指向my_decorator返回的wrapped举瑰;
2、然后wrapped作為一個新的函數(shù)去調(diào)用蔬螟。
3嘶居、內(nèi)部函數(shù)wrapped被引用,外部函數(shù)的變量fn(即say_name)并沒有被釋放促煮,它保存的還是原來最初的定義邮屁。

2.3 python語法糖@的用法

稍微修改上面的輪子:

def my_decorator(fn):
    def wrapped(*args, **kwargs):
        new_name = kwargs['name'].replace('daocoder', 'mudai')
        new_age = args[0] + 1
        return fn(new_age, name=new_name)
    return wrapped


@my_decorator
def say_name(*args, **kwargs):
    return "my name is %s and %d years old" % (kwargs['name'], args[0])


print(say_name(26, name='daocoder'))

結(jié)果:

my name is mudai and 27 years old

那說明這個my_decorator(say_name)@my_decorator這兩個的作用就是一樣的。一個語法糖包裝吧菠齿。

@my_decorator == my_decorator(say_name)

2.4 類裝飾器

上面的介紹可以看出佑吝,裝飾器需要callable對象作為參數(shù),然后返回一個callback對象绳匀。一般在python中callable對象都是函數(shù)芋忿,但也有例外炸客,只有某個類中寫了__call__方法,那么對象就是callable的戈钢。

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

    def __call__(self, *args, **kwargs):
        # print(args)
        # print(kwargs)
        # print(self.func)
        new_name = kwargs['name'].replace('daocoder', 'mudai')
        new_age = args[0] + 1
        return self.func(new_age, name=new_name)


@MyDecorator
def say_name(*args, **kwargs):
    return "my name is %s and %d years old" % (kwargs['name'], args[0])


print(say_name(26, name='daocoder'))

結(jié)果:

my name is mudai and 27 years old

以上大體調(diào)用過程如下:

1痹仙、MyDecorator作為裝飾器對say_name進行裝飾的時候,這時先去創(chuàng)建MyDecorator這個實例對象殉了。
2开仰、創(chuàng)建對象初始化時,會把say_name這個函數(shù)名當(dāng)作參數(shù)傳遞到__init__方法中薪铜,這里保存say_name作為對象的func屬性众弓。
3、這時say_name指向的是MyDecorator的實例對象隔箍,然后調(diào)用say_name()谓娃,即調(diào)用MyDecorator的實例對象的call`方法。

這里和之前函數(shù)作為裝飾器的區(qū)別就是第3步:函數(shù)作為裝飾器時蜒滩,say_name的指向應(yīng)該是返回的wrapped方法滨达,而這里say_name的指向是MyDecorator的實例對象。

3俯艰、感謝

Python中如何在一個函數(shù)中加入多個裝飾器

某平臺培訓(xùn)資料弦悉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蟆炊,隨后出現(xiàn)的幾起案子稽莉,更是在濱河造成了極大的恐慌,老刑警劉巖涩搓,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件污秆,死亡現(xiàn)場離奇詭異,居然都是意外死亡昧甘,警方通過查閱死者的電腦和手機良拼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來充边,“玉大人庸推,你說我怎么就攤上這事〗奖” “怎么了贬媒?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肘习。 經(jīng)常有香客問我际乘,道長,這世上最難降的妖魔是什么漂佩? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任脖含,我火速辦了婚禮罪塔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘养葵。我一直安慰自己征堪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布关拒。 她就那樣靜靜地躺著佃蚜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪夏醉。 梳的紋絲不亂的頭發(fā)上爽锥,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天涌韩,我揣著相機與錄音畔柔,去河邊找鬼。 笑死臣樱,一個胖子當(dāng)著我的面吹牛靶擦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雇毫,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼玄捕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了棚放?” 一聲冷哼從身側(cè)響起枚粘,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎飘蚯,沒想到半個月后馍迄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡局骤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片完域。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡栏渺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凯傲,到底是詐尸還是另有隱情犬辰,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布冰单,位于F島的核電站忧风,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏球凰。R本人自食惡果不足惜狮腿,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一腿宰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缘厢,春花似錦吃度、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至英遭,卻和暖如春间护,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挖诸。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工汁尺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人多律。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓痴突,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狼荞。 傳聞我的和親對象是個殘疾皇子辽装,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

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

  • 〇、前言 本文共108張圖相味,流量黨請慎重拾积! 歷時1個半月,我把自己學(xué)習(xí)Python基礎(chǔ)知識的框架詳細梳理了一遍丰涉。 ...
    Raxxie閱讀 18,959評論 17 410
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line)拓巧,也就是一...
    悟名先生閱讀 4,150評論 0 13
  • 我感嘆命運之手,不知何從…… 你昔搂,是新中國最早的拿了紅本本的赤腳醫(yī)生玲销,可以靠知識吃飯但最終卻昄依佛門。你摘符,有一場轟...
    四字真言閱讀 596評論 4 1
  • 本故事并非虛構(gòu)贤斜,如有雷同,請點喜歡逛裤。 從來都是我取笑爸媽親戚朋友被詐騙上當(dāng)什么的瘩绒,萬萬沒想到啊自己這次被結(jié)結(jié)實實弄...
    izan閱讀 251評論 0 1
  • 當(dāng)你對一位貌美如花的女子一見鐘情時,二見傾心時带族,又苦苦暗戀不得锁荔,又輾轉(zhuǎn)反側(cè),夜不能寐時的我想你朝思暮想時蝙砌,是否想用...
    四夕清禾閱讀 6,251評論 3 11