再談python裝飾器

人生苦短朋譬,我用Python

__ 文:鄭元春__


1. 一切都是參數(shù)

首先盐茎,在python中什么都可以傳遞,不只是一般的變量徙赢,對象,python還具有面向函數(shù)編程的思想字柠,所以你寫的任何的函數(shù)也可以當(dāng)做是參數(shù)傳遞給另一個函數(shù)。只要記住狡赐,在python中什么都能傳遞窑业。

函數(shù)的作用當(dāng)然是實現(xiàn)功能了,所以作為另一個函數(shù)的參數(shù)的時候就把當(dāng)前函數(shù)的功能帶過去了(只要調(diào)用就行枕屉,和一般調(diào)用的區(qū)別就是這是軟編碼的常柄,并不是硬調(diào)用的),同時還可以增添一些其他的功能搀擂。這就是在不改變原來函數(shù)的情況下西潘,我們只需要編寫額外的功能在另一個函數(shù)中,實現(xiàn)動態(tài)“對功能打補丁”的效果哨颂。


2. 一般的調(diào)用

一般的調(diào)用就是硬編碼調(diào)用喷市,在另一個函數(shù)中直接實現(xiàn)對當(dāng)前函數(shù)的調(diào)用,這種調(diào)用只能實現(xiàn)一對一的調(diào)用威恼。

def func1():
    print ("func1")

def func2():
    print("before")
    func1()
    print("after")

我們在func2函數(shù)中調(diào)用了func1函數(shù)品姓,但這是硬編碼的,當(dāng)我們有了另外一個函數(shù)func11的時候箫措,我們要想實現(xiàn)func2的之前和之后輸出的時候腹备,我們還得寫另外一個函數(shù)來包裝,所以這種方式是一個比較“硬”的編碼方式斤蔓。


3. 面向函數(shù)編程思想

既然在python中可以將函數(shù)都當(dāng)做參數(shù)傳遞植酥,那么我們可以嘗試著做如下的改變。

def func1():
    print("func1")

def func2():
    print("func2")

def wrapper(func):
    print("before")
    func()
    print("after")
    return func  #這里的return很重要附迷,要不然調(diào)用一次wrapper之后原先的func就成了空了
func1=wrapper(func1)
func2=wrapper(func2)
f=wrapper(func1)    #這里f就是func1函數(shù)

這里編寫了wrapper函數(shù)之后惧互,直接將需要“打補丁”的函數(shù)當(dāng)做參數(shù)傳遞進去就可以,注意這里的wrapper最后將傳遞進來的函數(shù)又return回去了喇伯,也就是說wrapper只做了增添功能的動作喊儡,最后并返回了原先的函數(shù),這樣我們使用func1=wrapper(func1)操作的時候稻据,func1還是原來的函數(shù)并沒有做任何的改變艾猜,如果最后沒有這一句return的話买喧,那么使用func1=wrapper(func1)就對func1進行了重寫,重寫的結(jié)果是func1變成了空匆赃,這不是我們想要的結(jié)果淤毛,所以一定要記住這個返回語句。

我們可以這樣寫算柳,但是這樣寫并不能體現(xiàn)出python的牛逼來低淡,所以python中有一個語法糖是這么實現(xiàn)的。我們稱它為裝飾器(decorator)瞬项。


4. 神奇的decorator

python使用裝飾器將上面的函數(shù)重寫變成了另一種十分簡潔的方式:

def wrapper(func):
    print("before")
    func()
    print("after")
    return func

@wrapper
def f()
    print("call f")

如果你將上面的腳本保存成python文件并運行你可以看到輸出以下內(nèi)容:

before
call f
after

其實裝飾器此時會將代碼轉(zhuǎn)換成:f=wrapper(f)蔗蹋,完成的實際上函數(shù)的重寫(增添了wrapper的功能),為了保持原來的函數(shù)的本質(zhì)囱淋,此時一定不要忘記在wrapperreturn原來的函數(shù)猪杭。但是你會發(fā)現(xiàn)上面的腳本的輸出方式并不是我們預(yù)計的輸出結(jié)果,腳本首先自動輸出了wrapper的結(jié)果妥衣,當(dāng)我們顯示調(diào)用的時候卻只保持了原來函數(shù)的輸出皂吮。我們分析代碼,關(guān)鍵字@就是一個函數(shù)重寫語句税手,相當(dāng)于f=wrapper(f)所以此時調(diào)用了wrapper函數(shù)并在wrapper內(nèi)部調(diào)用了f函數(shù)蜂筹。此時完成了f的重寫,還是原來的函數(shù)功能冈止。

我們希望函數(shù)在加了裝飾器之后能夠保持住“打補丁”之后的升級功能狂票,并不是指調(diào)用一次。一個自然而然的做法就是將wrapper函數(shù)在封裝一層熙暴,函數(shù)重寫的時候并不是重寫的原來的函數(shù)闺属,而是“打補丁”之后的函數(shù)姥敛,返回的是一個函數(shù)句柄团赁,如果我們不去顯示調(diào)用的話,不會執(zhí)行任何功能丢烘。


5. 第一個具有實際功能的decorator

我們將裝飾器再封裝一層俱箱。返回的是真實功能的函數(shù)句柄国瓮。這樣既能保持升級后的函數(shù)所有功能,還能防止自動執(zhí)行封裝器狞谱。

def wrapper(func):
    def inner():
        print("before")
        func()
        print("after")
    return inner

#這里相當(dāng)于 f=wrapper(f)乃摹,此時f重寫為inner的函數(shù)句柄
@wrapper
def f():
    print("call f")

f()  #如果不顯示的執(zhí)行的話,裝飾器并沒有任何執(zhí)行過程

這樣上面寫的封裝器在內(nèi)部又封裝了一層跟衅,返回是一個inner函數(shù)的句柄孵睬,只要不顯示的調(diào)用就不會執(zhí)行。

同時伶跷,多個裝飾器還可以疊加使用

def wrapper1(func):
    def inner():
        print("before wrapper 1")
        func()
        print("after wrapper 2")
    return inner

def wrapper(func):
    def inner():
        print("before wrapper 2")
        func()
        print("after wrapper 2")
    return inner

@wrapper1
@wrapper2
def f():
    print("call f")

f()

[out]:before wrapper 1
      before wrapper 2
      call f
      after wrapper 2
      after wrapper 1

6. 帶參數(shù)函數(shù)的裝飾器

上面的裝飾器的例子是最簡單的例子掰读,并沒有牽扯到任何的參數(shù)信息秘狞。但是一般的函數(shù)都是帶著參數(shù)的,讓我們看看帶參函數(shù)怎么寫裝飾器蹈集。

首先我們分析烁试,之前的wrapper函數(shù)返回的是inner函數(shù)的函數(shù)句柄,inner函數(shù)會在裝飾器執(zhí)行的時候重寫到實際的函數(shù)中拢肆,所以說可以將原先函數(shù)的參數(shù)交給inner函數(shù)來作為傳遞的橋梁减响。

def wrapper(func):
    def inner(a,b):
        print("before wrapper")
        func(a,b)
        print("after wrapper")
    return inner

@wrapper
def f(a,b):
    print ("call f: a+b=%d"% (a+b) )

f(2,3)

上面的裝飾器相當(dāng)于f=wrapper(f)=inner,然后可以將參數(shù)再加進去善榛,就"相當(dāng)于"參數(shù)傳遞了辩蛋。


7. 參數(shù)個數(shù)不確定

有的時候我們在寫裝飾器之前是不知道將要裝飾的函數(shù)到底有幾個參數(shù)的呻畸,所以就不能使用基于位置的參數(shù)表示方式移盆,我們使用(*args, **kwargs)來自動適應(yīng)變參和命名參數(shù)

#coding:utf-8
def wrapper(func):
    def inner(*args, **kwargs):
        print("before %s"%func.__name__)
        result=func(*args,**kwargs)
        print("result of %s is %d"%(func.__name__,result))
    return inner

@wrapper
def f1(a,b):
    print("call f1")
    return a+b
@wrapper
def f2(a,b,c):
    print("call f2")
    return a+b+c

f1(1,2)
f2(2,3,4)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市伤为,隨后出現(xiàn)的幾起案子咒循,更是在濱河造成了極大的恐慌,老刑警劉巖绞愚,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叙甸,死亡現(xiàn)場離奇詭異,居然都是意外死亡位衩,警方通過查閱死者的電腦和手機裆蒸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糖驴,“玉大人僚祷,你說我怎么就攤上這事≈疲” “怎么了辙谜?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長感昼。 經(jīng)常有香客問我装哆,道長,這世上最難降的妖魔是什么定嗓? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任蜕琴,我火速辦了婚禮,結(jié)果婚禮上宵溅,老公的妹妹穿的比我還像新娘凌简。我一直安慰自己,他們只是感情好层玲,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布号醉。 她就那樣靜靜地躺著反症,像睡著了一般。 火紅的嫁衣襯著肌膚如雪畔派。 梳的紋絲不亂的頭發(fā)上铅碍,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音线椰,去河邊找鬼胞谈。 笑死,一個胖子當(dāng)著我的面吹牛憨愉,可吹牛的內(nèi)容都是我干的烦绳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼配紫,長吁一口氣:“原來是場噩夢啊……” “哼径密!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起躺孝,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤享扔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后植袍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惧眠,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年于个,在試婚紗的時候發(fā)現(xiàn)自己被綠了氛魁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡厅篓,死狀恐怖秀存,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贷笛,我是刑警寧澤应又,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站乏苦,受9級特大地震影響株扛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汇荐,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一洞就、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掀淘,春花似錦旬蟋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冕碟。三九已至,卻和暖如春匆浙,著一層夾襖步出監(jiān)牢的瞬間安寺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工首尼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挑庶,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓软能,卻偏偏與公主長得像迎捺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子查排,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

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

  • Python進階框架 希望大家喜歡凳枝,點贊哦首先感謝廖雪峰老師對于該課程的講解 一、函數(shù)式編程 1.1 函數(shù)式編程簡...
    Gaolex閱讀 5,502評論 6 53
  • 要點: 函數(shù)式編程:注意不是“函數(shù)編程”雹嗦,多了一個“式” 模塊:如何使用模塊 面向?qū)ο缶幊蹋好嫦驅(qū)ο蟮母拍罘兑ā傩浴?..
    victorsungo閱讀 1,521評論 0 6
  • 兩本不錯的書: 《Python參考手冊》:對Python各個標(biāo)準(zhǔn)模塊,特性介紹的比較詳細(xì)了罪。 《Python核心編程...
    靜熙老師哈哈哈閱讀 3,362評論 0 80
  • 微信名:洋洋灑灑 作業(yè)1:好好回想一下,小時候呆呆看過什么聪全,或者有什么經(jīng)常見到的事情(如廣場舞)泊藕,挑選一個印象或一...
    奧利維亞的暢想生活閱讀 400評論 3 0
  • 1. “我后天有個酒要喝娃圆,你自己吃飯吧《贶裕” “操讼呢,那家酒樓的菜難吃的一逼,酒更難喝谦炬≡闷粒”胖子坐在客廳啐了一口瓜子殼,...
    谷谷閱讀 686評論 13 7