Python裝飾器

# 這是一個裝飾器的簡單例子

@dec
def func():
    pass

什么是裝飾器

裝飾器可以在不改變原有對象的代碼及調用方式的情況下,為原有的對象增加新的功能或限制條件。裝飾器有函數(shù)裝飾器,也有類裝飾器面哥。裝飾器體現(xiàn)的是開放封閉原則,即對功能擴展的開放毅待,對修改已實現(xiàn)功能的封閉尚卫。

裝飾器本質上就是Python的函數(shù),它和普通的函數(shù)沒有任何區(qū)別尸红,只是有了特殊的用法吱涉,所以在特殊用法下就有了裝飾器這個名字。

裝飾器怎么用

現(xiàn)在有一個hello函數(shù)外里,功能很簡單怎爵,只是打印hello world!

def hello():
    print('hello world!')
    
hello()

它的執(zhí)行結果為:

hello world!

現(xiàn)在要對這個函數(shù)進行一下功能的擴展盅蝗。

def dec(func): # 這個就是裝飾器鳖链,參數(shù)就是被裝飾的函數(shù)
    def wrapper():
        print('start...')
        func()
        print('end...')
    return wrapper
    
@dec # 這是裝飾器的用法
def hello(): 
    print('hello world!')
    
hello() #函數(shù)的調用方法和上邊一樣沒有改變    

現(xiàn)在的執(zhí)行結果為:

start...
hello world!
end...

如上面的代碼顯示,函數(shù)dec就是裝飾器墩莫,函數(shù)hello就是被裝飾的函數(shù)芙委。而裝飾器的用法,就是hello函數(shù)上面的@dec狂秦。注意灌侣,這里應用裝飾器只是寫了dec這個函數(shù)名,而不是dec()這樣調用它故痊。

裝飾器的運行機制

還是上面的例子顶瞳。

  1. 解釋器讀到函數(shù)dec,也就是裝飾器愕秫。這時它還沒有被調用,所以只是被存在了內(nèi)存中焰络,并沒有被執(zhí)行戴甩。
  2. 到了@dec。@dec和dec()一樣闪彼,都會執(zhí)行這個函數(shù)甜孤,但是不同的是协饲,@dec會把被裝飾的hello函數(shù)的本身(而不是函數(shù)執(zhí)行結果)當作參數(shù),傳入到dec函數(shù)中缴川,相當于執(zhí)行了dec(hello)這個函數(shù)茉稠。
  3. 這時開始執(zhí)行dec函數(shù)。首先會把wrapper函數(shù)存入到內(nèi)存中(并不是執(zhí)行)把夸,然后返回wrapper函數(shù)本身(也不是執(zhí)行)給hello函數(shù)而线。也就是,hello函數(shù)被裝飾完之后恋日,內(nèi)存地址被指向到了wrapper函數(shù)的內(nèi)存地址膀篮。
  4. 執(zhí)行hello函數(shù)。由于hello函數(shù)的地址已經(jīng)被指向到了wrapper函數(shù)岂膳,所以執(zhí)行結果就是wrapper函數(shù)的執(zhí)行結果誓竿。wrapper函數(shù)中的func這個函數(shù)地址才是原來hello函數(shù)的內(nèi)存地址。所以使用裝飾器之后谈截,表面上不會改變原來函數(shù)調用的方法筷屡。

以上就是裝飾器的運行機制。

為什么是嵌套函數(shù)

剛開始學習裝飾器的人可能會比較疑惑簸喂,裝飾器為什么要用嵌套函數(shù)毙死?感覺好多功能不用嵌套也可以實現(xiàn)。下面看例子娘赴。

def dec(func):    
    print('start...')    
    func()    
    print('end...')

@dec
def hello():    
    print('hello world!')

執(zhí)行結果為:

start...
hello world!
end...

這個執(zhí)行結果看起來沒有問題规哲。是的,執(zhí)行結果是沒有問題诽表,但是問題是還沒有寫執(zhí)行語句卻已經(jīng)有了執(zhí)行結果唉锌。

造成這個問題的原因就在@dec這里。@dec就是執(zhí)行裝飾器的語句竿奏,所以它就執(zhí)行了dec這個函數(shù)袄简。這就是為什么,還沒有寫執(zhí)行語句卻有了執(zhí)行結果的原因泛啸。這也是為什么裝飾器要用嵌套函數(shù)的原因绿语,再用一個函數(shù)封裝一下,避免在定義階段就給執(zhí)行了候址。

被裝飾的函數(shù)帶參數(shù)

def hello(string):
    print(f'hello {string}!')
    
    
hello('python')

執(zhí)行結果為:

hello python!

這是一個帶參數(shù)的函數(shù)吕粹,那么這種函數(shù)如何添加裝飾器呢?

其實岗仑,只要理解了被裝飾的函數(shù)會被指向到裝飾器里的函數(shù)匹耕,那么在裝飾器里的函數(shù)加上參數(shù)就解決了傳參的問題。下面看例子荠雕。

def dec(func):
    def wrapper(string): # 添加和hello一樣的參數(shù)即可
        print('start...')
        func(string) # 通過wrapper函數(shù)傳遞來的參數(shù)
        print('end...')
    return wrapper

@dec
def hello(string): # 內(nèi)存地址會被指向wrapper的內(nèi)存地址
    print(f'hello {string}!')

hello('python')

執(zhí)行的結果為:

hello python!

裝飾器帶參數(shù)

如果理解了前邊的內(nèi)容稳其,那么理解裝飾器帶參數(shù)也就比較容易了驶赏。下面先看例子。

def outer(name): # 多了一層函數(shù)既鞠,用于接收傳遞裝飾器的參數(shù)
    def dec(func):
        def wrapper(string):
            print(f'{name} start...')
            func(string)
            print(f'{name} end...')
        return wrapper
    return dec

@outer('haha')  # 裝飾器執(zhí)行語句多了括號和參數(shù)
def hello(string):
    print(f'hello {string}!')

hello('python')

執(zhí)行結果為:

haha start...
hello python!
haha end...

首先可以看到煤傍,裝飾器在定義的時候又多了一層嵌套,這層嵌套用于接收傳遞參數(shù)嘱蛋,并且返回下一層的嵌套函數(shù)蚯姆。

然后可以看到裝飾器多了括號和參數(shù)『荆現(xiàn)在說的就是帶參數(shù)的裝飾器共啃,所以帶參數(shù)沒有什么稀奇的。

最主要的是多的那個括號夫嗓。裝飾器執(zhí)行語句多了括號之后桐玻,它的意思也稍微有了變化篙挽。我們知道,執(zhí)行函數(shù)就是函數(shù)名加上括號镊靴。那么 @outer('haha') 的執(zhí)行順序是下面這樣的铣卡。

  1. 先執(zhí)行outer('haha')這個函數(shù),這時它的參數(shù)是'haha'偏竟。
  2. outer函數(shù)返回值是dec煮落,所以@outer('haha') = @dec,并把參數(shù)傳到了函數(shù)內(nèi)踊谋。
  3. @dec這個語句就是我們前邊熟悉的裝飾器的執(zhí)行語句了蝉仇。它會把hello函數(shù)本身當作參數(shù)傳遞給函數(shù)dec,并把內(nèi)存地址指向到了wrapper函數(shù)殖蚕。后邊執(zhí)行過程就跟前邊的例子一樣轿衔,也就不再贅述了。

主要知道@outer()會先執(zhí)行outer函數(shù)睦疫,而不是把hello當作參數(shù)的裝飾器語句害驹,那么對于帶參數(shù)的裝飾器也就沒什么難點了。

疊加裝飾器

裝飾器也可以疊加使用蛤育,還是先看例子宛官。

def dec1(func):
    print('這是第一層')
    def wrapper():
        print('這是第1層開始')
        func()
        print('這是第1層結束')
    return wrapper

def dec2(func):
    print('這是第二層')
    def wrapper():
        print('這是第2層開始')
        func()
        print('這是第2層結束')
    return wrapper

@dec2
@dec1
def hello():
    print('hello world!')

print('開始')
hello()

執(zhí)行結果為:

這是第一層
這是第二層
開始
這是第2層開始
這是第1層開始
hello world!
這是第1層結束
這是第2層結束

這個結果可能剛開始看有些不那么好理解,我們慢慢看瓦糕。

先看執(zhí)行結果的前三行底洗。這三行內(nèi)容里,首先執(zhí)行的是兩個裝飾器的內(nèi)容咕娄,之后才是print語句枷恕。從這個執(zhí)行結果可以看出,python是從上到下順序執(zhí)行谭胚,并在遇到裝飾器執(zhí)行語句的時候會自動執(zhí)行裝飾器函數(shù)('第幾層開始'沒有跟著執(zhí)行是因為被封裝進了函數(shù)徐块,要執(zhí)行語句才能執(zhí)行)。再從首先執(zhí)行了第一層又執(zhí)行了第二層可以看出灾而,遇到疊加裝飾器時是從下向上執(zhí)行的胡控。

明白了裝飾器是從下向上執(zhí)行,那么就是下一層裝飾器的函數(shù)就是它上一層裝飾器的參數(shù)旁趟。

@dec2  # 相當與dec2(dec1(hello))
@dec1  # 相當于dec1(hello)
def hello():

這么看這個執(zhí)行結果就好理解了昼激。

結束

這篇文章主要是幫助初步理解裝飾器和一些簡單的用法,在這里就不詳細說明其他更深奧的用法了(主要是我不會)锡搜,那么這篇文章也就到這里了橙困。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市耕餐,隨后出現(xiàn)的幾起案子凡傅,更是在濱河造成了極大的恐慌,老刑警劉巖肠缔,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夏跷,死亡現(xiàn)場離奇詭異,居然都是意外死亡明未,警方通過查閱死者的電腦和手機槽华,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趟妥,“玉大人猫态,你說我怎么就攤上這事∨悖” “怎么了亲雪?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長行疏。 經(jīng)常有香客問我匆光,道長,這世上最難降的妖魔是什么酿联? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任终息,我火速辦了婚禮,結果婚禮上贞让,老公的妹妹穿的比我還像新娘周崭。我一直安慰自己,他們只是感情好喳张,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布续镇。 她就那樣靜靜地躺著,像睡著了一般销部。 火紅的嫁衣襯著肌膚如雪摸航。 梳的紋絲不亂的頭發(fā)上制跟,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音酱虎,去河邊找鬼雨膨。 笑死,一個胖子當著我的面吹牛读串,可吹牛的內(nèi)容都是我干的聊记。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼恢暖,長吁一口氣:“原來是場噩夢啊……” “哼排监!你這毒婦竟也來了?” 一聲冷哼從身側響起杰捂,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤舆床,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后琼娘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峭弟,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年脱拼,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞒瘸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡熄浓,死狀恐怖情臭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赌蔑,我是刑警寧澤俯在,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站娃惯,受9級特大地震影響跷乐,放射性物質發(fā)生泄漏。R本人自食惡果不足惜趾浅,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一愕提、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧皿哨,春花似錦浅侨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春不见,著一層夾襖步出監(jiān)牢的瞬間澳化,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工脖祈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肆捕,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓盖高,卻偏偏與公主長得像,于是被迫代替她去往敵國和親眼虱。 傳聞我的和親對象是個殘疾皇子喻奥,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

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

  • 部分細節(jié)自己改了點,也加了點自己例子捏悬,基本上屬于轉載撞蚕。轉載出處:https://my.oschina.net/le...
    洛克黃瓜閱讀 1,970評論 0 3
  • 在python編程中,我們經(jīng)彻溃看到下面的函數(shù)用法: with open("test.txt", "w") as f...
    hugoren閱讀 816評論 0 0
  • 一甥厦、裝飾器的基本使用 在不改變函數(shù)源代碼的前提下,給函數(shù)添加新的功能寇钉,這時就需要用到“裝飾器”刀疙。 0.開放封閉原則...
    NJingZYuan閱讀 524評論 0 0
  • 在學習Python的過程中,我相信有很多人和我一樣扫倡,對Python的裝飾器一直覺得很困惑谦秧,我也是困惑了好久,并通過...
    愚灬墨閱讀 457評論 1 1
  • 文||木暖霏 目錄 李佳茗踉蹌地站起來奔向被炸的四分五裂的地方撵溃,相似的場景再一次重演疚鲤。五年前,十二月初的文安市缘挑,原...
    木暖霏閱讀 468評論 0 6