Python學(xué)習(xí)筆記·裝飾器

給一個(gè)函數(shù)添加新功能


1. 裝飾器介紹

def hello(func):
    def newfunc():
        print("hello", end = " ")
        func()
        return
    return newfunc
    
@hello
def xiaoli():
    print("xiaoli")
    return
xiaoli()
#輸出: hello xiaoli

如上面代碼所示担神,裝飾器是一個(gè)函數(shù) 可調(diào)用對(duì)象嘴秸,接收一個(gè)函數(shù)作為參數(shù),并將其替換成一個(gè)函數(shù)馅笙。

2. “裝飾”過程

分析1的代碼躲叼,函數(shù)xiaoli在定義時(shí)只會(huì)輸出"xiaoli",而調(diào)用時(shí)卻額外輸出了"hello "蓄坏,顯然我們的調(diào)用過程事實(shí)上調(diào)用的是hello內(nèi)部定義的newfunc价捧,并且func即為我們實(shí)際定義的xiaoli。
先無視第8行的@hello嘗試手動(dòng)實(shí)現(xiàn)這個(gè)效果涡戳。

def hello(func):
    def newfunc():
        print("hello", end = " ")
        func()
        return
    return newfunc
    
def xiaoli():
    print("xiaoli")
    return
xiaoli()
#輸出:xiaoli

去掉了@hello干旧,正常的調(diào)用了xiaoli,這里和hello沒有什么關(guān)系妹蔽。

hello(xiaoli)
#無輸出

外層函數(shù)hello被調(diào)用,查看返回值發(fā)現(xiàn)返回一個(gè)函數(shù)對(duì)象挠将,根據(jù)hello的定義這里返回了內(nèi)層函數(shù)newfunc胳岂。
嘗試調(diào)用它:

hello(xiaoli)()
#輸出:hello xiaoli

這樣輸出就和1的示例一致了,為了讓調(diào)用過程也保持一致舔稀,加入一行代碼:

xiaoli = hello(xiaoli)
xiaoli()
#輸出:hello xiaoli

這樣就實(shí)現(xiàn)了和1中示例代碼同樣的效果乳丰。對(duì)于那個(gè)@hello我們可以理解為它就是在函數(shù)定義結(jié)束后添加了一行xiaoli = hello(xiaoli)。至少目前看來這二者沒有什么區(qū)別内贮。

3. 裝飾帶參數(shù)和返回值的函數(shù)

了解了裝飾器的用法后我們可以試圖讓所有自己定義的函數(shù)都變得懂禮貌产园,只需要在定義時(shí)使用hello裝飾器即可汞斧。如:

@hello
def xiaoyu():
    print("xiaoyu")
    return    
@hello
def xiaoliu():
    print("xiaoliu")
    return    
@hello
def xiaozhou():
    print("xiaozhou")
    return

充分發(fā)揚(yáng)懶惰精神,我們可以寫成這樣

@hello
def xiaonashei(nashei):
    print(xiao + str(nashei))
    return

看上去就有毛病什燕,調(diào)用果然出錯(cuò):

In [133]: xiaonashei("nashei")
---------------------------------------------------------------------------
TypeError   Traceback (most recent call last)
<ipython-input-133-e12e640621a4> in <module>()
----> 1 xiaonashei("nashei")
TypeError: newfunc() takes 0 positional arguments but 1 was given

顯然是寫hello考慮不周粘勒,newfunc應(yīng)當(dāng)考慮不同參數(shù)情況。

def hello(func):
    def newfunc(*args):
        print("hello "屎即, end = "")
        func(*args)
        return 
    return newfunc

順便發(fā)現(xiàn)第5行內(nèi)部函數(shù)居然沒有返回值庙睡,一并解決。

def hello(func):
    def newfunc(*args):
        print("hello ", end = "")
        result = func(*args)
        return result
    return newfunc

然后就可以愉快地裝飾xiaonashei了技俐。

@hello
def xiaonashei(nashei):
    print(xiao + str(nashei))
    return

xiaonashei("penyou")
#輸出:hello xiaopenyou

帶返回值的函數(shù)也能正確獲取返回值乘陪。

@hello
def bbs(nasha):
    return abs(nasha)

b = bbs(-1)
#輸出: hello
print(b)
#輸出: 1

4. 參數(shù)化裝飾器

既然裝飾器也是函數(shù),理應(yīng)可以接受其他參數(shù)以實(shí)現(xiàn)不同功能雕擂,嘗試根據(jù)性別打招呼啡邑。

def hello(func,sex):
    def newfuncforboy(*args):
        print("hello boy ", end = "")
        result = func(*args)
        return result
    def newfuncforgirl(*args):
        print("hello girl ", end = "")
        result = func(*args)
        return result
    return newfuncforboy if sex == "m" else newfuncforgirl

運(yùn)行試試

In [130]: @hello("m")
     ...: def xiaoxiao():
     ...:     print("xiaoxiao")
     ...:
---------------------------------------------------------------------------
TypeError   Traceback (most recent call last)
<ipython-input-130-fd7cd46b49f4> in <module>()
----> 1 @hello("m")
      2 def xiaoxiao():
      3     print("xiaoxiao")
      4
TypeError: hello() missing 1 required positional argument: 'sex'

為什么會(huì)這樣?回憶2里對(duì)@hello的替換井赌,

    @hello("m")
    def xiaoxiao():
    ...

看成

    def xiaoxiao():
    ...
    xiaoxiao = hello("m")(xiaoxiao)

也就是說谤逼,xiaoxiao并不會(huì)被當(dāng)做參數(shù)傳入hello,而是被傳入hello("m")族展,事實(shí)上森缠,在那之前代碼就會(huì)因?yàn)閔ello("m")這個(gè)錯(cuò)誤的函數(shù)調(diào)用而中止。
回憶1講過的內(nèi)容仪缸,裝飾器是一個(gè)可調(diào)用對(duì)象贵涵,接收一個(gè)函數(shù)作為參數(shù),并將其替換成一個(gè)函數(shù)。@符號(hào)永遠(yuǎn)認(rèn)為其緊跟著的是一個(gè)裝飾器恰画,在無參的情況下hello是一個(gè)裝飾器宾茂,在帶參數(shù)的情況下hello("m")也應(yīng)當(dāng)是一個(gè)裝飾器。也就是說hello("m")的返回值才應(yīng)當(dāng)是作用于xiaoxiao的裝飾器拴还。
再加入一層函數(shù)嵌套:

def hello(sex):
    def inner(func):
        def newfuncforboy(*args):
            print("hello boy ", end = "")
            result = func(*args)
            return result
        def newfuncforgirl(*args):
            print("hello girl ", end = "")
            result = func(*args)
            return result
        return newfuncforboy if sex == "m" else newfuncforgirl
    return inner
    
@hello("m")
def xiaoming():
    print("xiaoming")
    return
    
@hello("w")
def xiaohong():
    print("xiaohong")
    return

xiaoming()
#輸出: hello boy xiaoming
xiaohong()
#輸出: hello girl xiaohong

終于正常實(shí)現(xiàn)功能了跨晴。
事實(shí)上如果使用一開始的寫法,手動(dòng)裝飾片林。

def hello(func,sex):
    def newfuncforboy(*args):
        print("hello boy ", end = "")
        result = func(*args)
        return result
    def newfuncforgirl(*args):
        print("hello girl ", end = "")
        result = func(*args)
        return result
    return newfuncforboy if sex == "m" else newfuncforgirl
   
def xiaoming():
    print("xiaoming")
    return
xiaoming = hello(xiaoming,"m")

def xiaohong():
    print("xiaohong")
    return
xiaohong = hello(xiaohong,"w")

xiaoming()
#輸出: hello boy xiaoming
xiaohong()
#輸出: hello girl xiaohong

好像同樣能夠?qū)崿F(xiàn)這一功能端盆,但是這樣會(huì)導(dǎo)致疊放裝飾器的時(shí)候很麻煩,所以還是建議使用前面的寫法费封。

5. 疊放裝飾器

可以疊放多個(gè)裝飾器焕妙,裝飾器自下而上作用。

def hell0(func):
    print("!hell0")
    def newfunc():
        print("hell0", end = " ")
        func()
        return
    return newfunc
    
def hell1(func):
    print("!hell1")
    def newfunc():
        print("hell1", end = " ")
        func()
        return
    return newfunc

def hell2(func):
    print("!hell2")
    def newfunc():
        print("hell2", end = " ")
        func()
        return
    return newfunc

@hell2
@hell1
@hell0
def xiaoli():
    print("xiaoli")
    return
    
xiaoli()

運(yùn)行上面的代碼弓摘,觀察輸出時(shí)機(jī)和順序即可焚鹊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市韧献,隨后出現(xiàn)的幾起案子末患,更是在濱河造成了極大的恐慌研叫,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件璧针,死亡現(xiàn)場(chǎng)離奇詭異嚷炉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)陈莽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門渤昌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人走搁,你說我怎么就攤上這事独柑。” “怎么了私植?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵忌栅,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我曲稼,道長(zhǎng)索绪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任贫悄,我火速辦了婚禮瑞驱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窄坦。我一直安慰自己唤反,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布鸭津。 她就那樣靜靜地躺著彤侍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逆趋。 梳的紋絲不亂的頭發(fā)上盏阶,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音闻书,去河邊找鬼名斟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛魄眉,可吹牛的內(nèi)容都是我干的砰盐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼杆融,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了霜运?” 一聲冷哼從身側(cè)響起脾歇,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蒋腮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后藕各,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體池摧,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年激况,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了作彤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乌逐,死狀恐怖竭讳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浙踢,我是刑警寧澤绢慢,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站洛波,受9級(jí)特大地震影響胰舆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蹬挤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一缚窿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧焰扳,春花似錦倦零、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至畜份,卻和暖如春诞帐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爆雹。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工停蕉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钙态。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓慧起,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親册倒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚓挤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,149評(píng)論 0 13
  • Scala與Java的關(guān)系 Scala與Java的關(guān)系是非常緊密的!灿意! 因?yàn)镾cala是基于Java虛擬機(jī)估灿,也就是...
    燈火gg閱讀 3,444評(píng)論 1 24
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,234評(píng)論 0 4
  • (圖片來自網(wǎng)絡(luò)) 如今,離開微信缤剧,生活會(huì)缺了一道滋味馅袁。微信世界里,人們每天的必修課是看新聞荒辕、發(fā)信息汗销、回信息、發(fā)紅包...
    珠珠姑娘閱讀 659評(píng)論 0 5
  • 你靜抵窒,掛一串星河 你鬧弛针,動(dòng)一城春色 你哭,流一江輕愁 你愛估脆,寫一世邂逅
    時(shí)正謀芳閱讀 146評(píng)論 0 0