讓Python的裝飾器不再難理解

注意:

如果對閉包不了解的同學(xué)請移步到這里先,因為裝飾器要通過閉包來實現(xiàn)

前言

剛開始學(xué)Python的時候,裝飾器(decorator)一直是個讓人難以理解的東西,所以想通過這篇文章能夠帶你一步一步來理解Python裝飾器的原理

什么是裝飾器?

經(jīng)典的設(shè)計模式有23種,設(shè)計模式其實也就是巨人們常年寫代碼經(jīng)驗的思想總結(jié),雖然說這是一種思想,但是由于語法的限制沒有辦法輕易實現(xiàn)(比如說用C語言來實現(xiàn)組合模式).在面向?qū)ο蟮脑O(shè)計模式中, decorator被稱為裝飾模式,OOP的裝飾模式需要通過繼承和組合來實現(xiàn)瑰妄,而Python除了能支持OOP的decorator外益缎,直接從語法層次支持decorator。

下面開始介紹裝飾器的原理

業(yè)務(wù)邏輯:

假如你是簡書的開發(fā)者,剛開發(fā)了一個發(fā)布文章的功能:

def send():
    # 核心代碼
    print("發(fā)布成功")

然后你美滋滋得上傳代碼了,第二天產(chǎn)品經(jīng)理拿著刀來找你:你妹啊,隨便就能發(fā)布文章了?不用登錄的?

~.~然后還得再寫一個驗證登錄的邏輯,好了,寫完了:

def check_login():
    print("做登錄驗證")

然后你就開始糾結(jié)了,這段代碼怎么放呢?這樣?

def send():
    check_login()
    # 核心代碼
    print("發(fā)布成功")

這樣確實完成了功能,但是不太優(yōu)雅,耦合度太高,我們是要寫出高質(zhì)量代碼的攻城獅!!!咳咳

所以,我們希望在不改變send()代碼的前提下,還能做驗證登錄的操作,裝飾器就出現(xiàn)了

def check_login(func):
    def inner():
        print("做登錄驗證")
        print("開始發(fā)布文章")
        func()
    return inner

def send():
    # 核心代碼
    print("發(fā)布成功")

send = check_login(send) #0
send()

可以看到,注釋0處的代碼返回值已經(jīng)是inner函數(shù)對象了.在執(zhí)行send()的時候?qū)嶋H上就是在執(zhí)行inner(),這樣就能做到不改變原有函數(shù)代碼的前提下,提升函數(shù)的功能!

Python中,有一個語法糖可以不用寫注釋0處的代碼

@check_login #語法糖
def send():
    # 核心代碼
    print("發(fā)布成功")

調(diào)用send()結(jié)果一樣,所以說

@check_login #語法糖
def send():
    # 核心代碼
    print("發(fā)布成功")

send = check_login(send)

兩種寫法是等價的!

在上面的代碼可以看出來,裝飾器個函數(shù),它的參數(shù)是被裝飾的函數(shù),返回值也是一個函數(shù).

業(yè)務(wù)邏輯的變動

有一天你發(fā)現(xiàn),發(fā)布文章的代碼好像得接收用戶編寫文章的內(nèi)容和標(biāo)題才能發(fā)布出現(xiàn)顯示給用戶看哦(不然就是一堆假數(shù)據(jù)),然后你趕緊改了一下發(fā)布文章的邏輯代碼:

def send(title,content):
    # 核心代碼
    print("文章標(biāo)題: %s, 內(nèi)容:%s" % (title,content))

運行就報錯了,因為加上裝飾器之后調(diào)用send("title","content")是相當(dāng)于調(diào)用了inner(),但是裝飾器里的inner并沒有接收參數(shù),所以,應(yīng)該修改裝飾器中的代碼:

def check_login(func):
    def inner(*args, **kw):
        print("做登錄驗證")
        print("開始發(fā)布文章")
        func(*args,**kw) #0
    return inner

因為注釋0代碼處才是真正調(diào)用了發(fā)布文章的原函數(shù),所以得把參數(shù)傳回去,這樣就解決問題了,可以傳任意數(shù)量和任意類型的參數(shù)!!

新的問題

當(dāng)你想要拿到發(fā)布文章的結(jié)果,發(fā)布成功或者失敗:所以你得改發(fā)布文章的邏輯代碼:

@check_login
def send(title,content):
    # 核心代碼
    print("文章標(biāo)題: %s, 內(nèi)容:%s" % (title,content))
    return True

# 然后接收
result = send("Python","Python的裝飾器")
print(result)

輸出結(jié)果:
做登錄驗證
開始發(fā)布文章
文章標(biāo)題: Python, 內(nèi)容:Python的裝飾器
None

emmmmm? result怎么回事None呢?如果你能看懂前面內(nèi)容的話這個小bug對你來說就根本不在話下,修改裝飾器的代碼:

def check_login(func):
    def inner(*args, **kw):
        print("做登錄驗證")
        print("開始發(fā)布文章")
        func(*args,**kw)
    return inner

好了,一步一步到這.一個標(biāo)準(zhǔn)的裝飾器終于完成了!!

裝飾器的進(jìn)階

上面我們驗證登錄的裝飾器代碼中,驗證完之后都會print("開始發(fā)布文章")來做調(diào)試.但是,我們不止發(fā)布文章的時候要做驗證登錄的操作,發(fā)圖片,評論啊,私信啊,回復(fù)等等等...都是需要登錄驗證的裝飾器(裝飾器的優(yōu)勢體現(xiàn)出來了).然而,你在做其他操作的時候控制臺來了一句開始發(fā)布文章 (黑人問號臉????),這時候你就想,要是調(diào)試的語句可以控制就好了

裝飾器工廠

裝飾器是可以接收自定義參數(shù)的,然后返回另一個裝飾器.這樣看來的話外面的裝飾器其實就是個裝飾器工廠,根據(jù)傳來不同的參數(shù)生成不同的裝飾器:

def get_decorator(console):
    def check_log(func):
        def inner(*args,**kw):
            print(console)
            return func(*args,**kw)
        return inner
    return check_log
    

@get_decorator("開始發(fā)布文章")
def send(title,content):
    # 核心代碼
    print("文章標(biāo)題: %s, 內(nèi)容:%s" % (title,content))
    return True

result = send("Python","Python的裝飾器")
print(result)

get_decorator就是個裝飾器工廠,根據(jù)參數(shù)返回一個裝飾器

上面的等價拆分的話,等價于

check_login = get_decorator("開始發(fā)布文章")
inner = check_login(send)
send(title,content) = inner(title,content)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓤荔,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喇嘱,死亡現(xiàn)場離奇詭異茉贡,居然都是意外死亡,警方通過查閱死者的電腦和手機者铜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門腔丧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人作烟,你說我怎么就攤上這事愉粤。” “怎么了拿撩?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵衣厘,是天一觀的道長。 經(jīng)常有香客問我,道長影暴,這世上最難降的妖魔是什么错邦? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮型宙,結(jié)果婚禮上撬呢,老公的妹妹穿的比我還像新娘。我一直安慰自己妆兑,他們只是感情好魂拦,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搁嗓,像睡著了一般芯勘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腺逛,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天荷愕,我揣著相機與錄音,去河邊找鬼棍矛。 笑死路翻,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的茄靠。 我是一名探鬼主播茂契,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慨绳!你這毒婦竟也來了掉冶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤脐雪,失蹤者是張志新(化名)和其女友劉穎厌小,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體战秋,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡璧亚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了脂信。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片癣蟋。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖狰闪,靈堂內(nèi)的尸體忽然破棺而出疯搅,到底是詐尸還是另有隱情,我是刑警寧澤埋泵,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布幔欧,位于F島的核電站罪治,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏礁蔗。R本人自食惡果不足惜觉义,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浴井。 院中可真熱鬧谁撼,春花似錦、人聲如沸滋饲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屠缭。三九已至,卻和暖如春崭参,著一層夾襖步出監(jiān)牢的瞬間呵曹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工何暮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奄喂,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓海洼,卻偏偏與公主長得像跨新,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子坏逢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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

  • 本文為《爬著學(xué)Python》系列第四篇文章域帐。從本篇開始,本專欄在順序更新的基礎(chǔ)上是整,會有不規(guī)則的更新肖揣。 在Pytho...
    SyPy閱讀 2,489評論 4 11
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,504評論 25 707
  • 前言 Python的修飾器的英文名叫Decorator,當(dāng)你看到這個英文名的時候浮入,你可能會把其跟Design Pa...
    linheimx閱讀 627評論 0 4
  • 剛剛在微信上看到一篇文章龙优,里面有幾句話是這么說的"你嘴上念念不忘的人,早就把你給忘記了事秀;你一心一意喜歡的人彤断,早就移...
    實心實意閱讀 429評論 8 2
  • IconMarker 可以根據(jù)不同的平臺生成對應(yīng)的icon尺寸 下載地址:IconMarker
    lsdoy閱讀 493評論 0 50