python黑魔法---裝飾器(decorator)

python 是一門優(yōu)雅的語(yǔ)言哄陶,有些使用方法就像魔法一樣寝衫。裝飾器(decorator)就是一種化腐朽性為神奇的技巧紫岩。最近一直都在使用 Tornado 框架赡鲜,一直還是念念不忘 Flask 。Flask 是我最喜歡的 Python 框架钝诚,最早被它吸引也是源自它使用裝飾器這個(gè)語(yǔ)法糖(Syntactic sugar)來(lái)做 Router颖御,讓代碼看上去就感覺(jué)甜甜的。

Tornado 中的 Router 略顯平淡凝颇,懷念 Flask 的味道潘拱,于是很好奇的想知道 Flask 是如何使用這個(gè)魔法。通過(guò)閱讀 Flask 的源碼拧略,我們也可以為 Tornado 實(shí)現(xiàn)了一個(gè)裝飾器 Router芦岂。

當(dāng)然對(duì)于剛接觸 Python 的人,也許很容易理解裝飾器本質(zhì)是設(shè)計(jì)模式中的裝飾器模式辑鲤】唬可是 Python 通過(guò)@一個(gè)實(shí)現(xiàn)裝飾器的語(yǔ)法糖杠茬。本文的目的就是讓 @ 不再神秘月褥。

一切都是對(duì)象

Python 里一切都是對(duì)象弛随,當(dāng)然這不代表一切都是女朋友。函數(shù)也是對(duì)象宁赤,因而可以當(dāng)成參數(shù)傳遞舀透,例如:

def say_english():
    print 'hello'

def say_chinese():
    print '你好'

say_english()           # hello
say_chinese()           # 你好

def greet(say):
    say()

greet(say_english)      # hello
greet(say_chinese)      # 你好

我們的 greet 函數(shù)的參數(shù),也是一個(gè)函數(shù)對(duì)象决左°倒唬可以傳遞這個(gè)參數(shù)對(duì)象。我們調(diào)用greet的時(shí)候佛猛,greet 內(nèi)部進(jìn)行函數(shù)參數(shù)的調(diào)用惑芭。

裝飾模式

裝飾模式,顧名思義继找,就是在調(diào)用目標(biāo)函數(shù)之前遂跟,對(duì)這個(gè)函數(shù)對(duì)象進(jìn)行裝飾。比如一個(gè)對(duì)數(shù)據(jù)庫(kù)操作的方法婴渡,我們?cè)诓樵償?shù)據(jù)之前幻锁,需要連接一下數(shù)據(jù)庫(kù),當(dāng)查詢結(jié)束之后边臼,需要再把連接斷開(kāi)關(guān)閉哄尔。正常的邏輯如下:

def connect_db():
    print 'connect db'

def close_db():
    print 'close db'

def query_user():

    connect_db()
    print 'query the user'
    close_db()


query_user()            # connect db
                        # query the user
                        # close db

我們把 連接數(shù)據(jù)庫(kù)(connect_db) 和 關(guān)閉連接 (close_db)都封裝成了函數(shù)。 query_data 方法執(zhí)行我們查詢的具體邏輯柠并。這樣需要不同的查詢方法岭接,只需要把查詢的邏輯也封裝成一個(gè)方法就Okla

def query_user():
    print 'query some user'

def query_data(query):

    connect_db()
    query()
    close_db()

query_data(query_user)

把查詢的函數(shù)對(duì)象傳進(jìn)來(lái),符合開(kāi)篇說(shuō)的一切都是對(duì)象臼予。裝飾器完成啦亿傅。對(duì),就這么簡(jiǎn)單瘟栖,query_data 就是對(duì) query_user 的裝飾葵擎,當(dāng)然你還可以寫出 query_blog 等方法。

等等半哟,設(shè)想一種情況酬滤,在我們使用裝飾函數(shù)之前,項(xiàng)目的代碼已經(jīng)有了大量的 query_user方法的調(diào)用寓涨。如果使用了query_data 包裝盯串。我們就不得不把之前 query_user() 的地方統(tǒng)統(tǒng)替換成 query_data(query_user)。怎么樣才能減少對(duì)代碼的改動(dòng)呢戒良?

我們的出發(fā)點(diǎn)是為了保持之前的 query_user() 不改動(dòng)体捏,現(xiàn)在實(shí)際情況是調(diào)用 query_data(query_user)。如果 query_data 調(diào)用的時(shí)候,返回一個(gè)函數(shù)呢几缭?例如下面的代碼:

def query_user():
    print 'query some user'

def query_data(query):
    """ 定義裝飾器河泳,返回一個(gè)函數(shù),對(duì)query進(jìn)行wrapper包裝 """
    def wrapper():
        connect_db()
        query()
        close_db()
    return wrapper
# 這里調(diào)用query_data進(jìn)行實(shí)際裝飾(注意裝飾是動(dòng)詞)
query_user = query_data(query_user)
# 調(diào)用被裝飾后的函數(shù)query_user
query_user()

這樣一個(gè)完整的裝飾器就完成了年栓,比起前面的版本拆挥,我們不需要改動(dòng)之前寫好的 query_user 代碼。一個(gè)關(guān)鍵點(diǎn)在于query_data 調(diào)用的時(shí)候某抓,返回了一個(gè) wrapper 函數(shù)纸兔,而這個(gè)wrapper 函數(shù)執(zhí)行 query 函數(shù)調(diào)用前后的一些邏輯。另外一個(gè)關(guān)鍵就是調(diào)用裝飾器 query_data 裝飾函數(shù)否副。

語(yǔ)法糖@

前面的代碼汉矿,可以使用 python的裝飾器語(yǔ)法糖@,如下:

def query_data(query):

    def wrapper():
        connect_db()
        query()
        close_db()
    return wrapper

# 使用 @ 調(diào)用裝飾器進(jìn)行裝飾
@query_data
def query_user():
    print 'query some user'

query_user()

前面的 裝飾器 調(diào)用進(jìn)行裝飾的時(shí)候备禀,python 有一個(gè)語(yǔ)法糖负甸。
如果給裝飾器函數(shù)前面加一個(gè)@,我們可以理解為調(diào)用了一些裝飾器函數(shù)痹届,即 @query_data 等于 query_data()呻待。當(dāng)實(shí)際上,并不是這么使用队腐,而是這么一個(gè)整體:

@query_data
def query_user():
    print 'query some user'

等價(jià)于

query_user = query_data(query_user)

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

我們被裝飾的函數(shù)蚕捉,往往帶有參數(shù),因此通過(guò)裝飾器如何傳遞參數(shù)呢柴淘?回想一下迫淹,裝飾器函數(shù)針對(duì)被裝飾的函數(shù)進(jìn)行裝飾,使用的是返回一個(gè) wrapper 函數(shù)为严。其實(shí)這個(gè)函數(shù)可以等同于被裝飾的函數(shù)敛熬,只不過(guò) wrapper 還做了更多的事情。被裝飾的函數(shù)參數(shù)可以通過(guò) wrapper 傳遞第股。如下:

def query_data(query):

    def wrapper(count):
        connect_db()
        query(count)
        close_db()
    return wrapper

@query_data
def query_user(count):
    print 'query some user limit  {count}'.format(count=count)

query_user(count=100)       # connect db
                            # query some user limit  100
                            # close db

這樣就實(shí)現(xiàn)了被裝飾的函數(shù)傳遞參數(shù)应民。當(dāng)然,位置參數(shù)和關(guān)鍵字參數(shù)夕吻,可變參數(shù)都可以诲锹。

裝飾器參數(shù)

在 flask 中,對(duì)視圖函數(shù)的裝飾是裝飾器中傳遞 url 正則涉馅,即在裝飾器中傳遞參數(shù)归园,和被裝飾器的參數(shù)還不一樣。

@app.router('/user')
def user_page():
    return 'user page'

我們?nèi)绾味xrouter這個(gè)裝飾器呢稚矿?其實(shí)只要在原先的裝飾器外面再包裹一層庸诱,也就是針對(duì)裝飾器進(jìn)行裝飾捻浦。

def router(url):

    print 'router invoke url', url

    def query_data(query):

        print 'query_data invoke url', url

        def wrapper(count):
            connect_db()
            query(count)
            close_db()
        return wrapper
    return query_data

@router('/user')          # 首先調(diào)用了router函數(shù), 輸出 router invoke url /user桥爽, 進(jìn)行@裝飾朱灿,輸出 'query_data invoke url', url
def query_user(count):
    print 'query some user limit  {count}'.format(count=count)

query_user(count=100)   # connect db
                        # query some user limit  100
                        # close db

@router() 這個(gè)語(yǔ)法糖看上去讓人迷惑,其實(shí)也很好理解聚谁。這里可以看成兩個(gè)步驟
第一步是調(diào)用 router 這個(gè)函數(shù):

 query_data =  router('/user')

第二步則進(jìn)行裝飾:

@query_data
def query_user():
    pass

連起來(lái)的效果就是

query_user = query_data(query_user))

即

query_user = router("/user")(query_user))

現(xiàn)在回想母剥,@ 這個(gè)語(yǔ)法糖很甜吧滞诺。并且和python一樣很好理解形导,也十分常用。

當(dāng)然习霹,我們使用 裝飾器是為了實(shí)現(xiàn)一些需要包裝的方法朵耕,例如前面提到的 flask 的 router

有人已經(jīng)寫了一篇很棒的 Tutorial: Things which aren't magic - Flask and @app.route,可以參考加深對(duì)裝飾確定理解淋叶,裝飾器還有很多用途.

Enjoy~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阎曹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子煞檩,更是在濱河造成了極大的恐慌处嫌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斟湃,死亡現(xiàn)場(chǎng)離奇詭異熏迹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)凝赛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門注暗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人墓猎,你說(shuō)我怎么就攤上這事捆昏。” “怎么了毙沾?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵骗卜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我左胞,道長(zhǎng)膨俐,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任罩句,我火速辦了婚禮焚刺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘门烂。我一直安慰自己乳愉,他們只是感情好兄淫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蔓姚,像睡著了一般捕虽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坡脐,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天泄私,我揣著相機(jī)與錄音,去河邊找鬼备闲。 笑死晌端,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的恬砂。 我是一名探鬼主播咧纠,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泻骤!你這毒婦竟也來(lái)了漆羔?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤狱掂,失蹤者是張志新(化名)和其女友劉穎演痒,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體趋惨,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸟顺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了希柿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诊沪。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖曾撤,靈堂內(nèi)的尸體忽然破棺而出端姚,到底是詐尸還是另有隱情,我是刑警寧澤挤悉,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布渐裸,位于F島的核電站,受9級(jí)特大地震影響装悲,放射性物質(zhì)發(fā)生泄漏昏鹃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一诀诊、第九天 我趴在偏房一處隱蔽的房頂上張望洞渤。 院中可真熱鬧,春花似錦属瓣、人聲如沸载迄。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)护昧。三九已至魂迄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惋耙,已是汗流浹背捣炬。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绽榛,地道東北人湿酸。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蒜田,于是被迫代替她去往敵國(guó)和親稿械。 傳聞我的和親對(duì)象是個(gè)殘疾皇子选泻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • # Python 資源大全中文版 我想很多程序員應(yīng)該記得 GitHub 上有一個(gè) Awesome - XXX 系列...
    aimaile閱讀 26,486評(píng)論 6 427
  • 22年12月更新:個(gè)人網(wǎng)站關(guān)停冲粤,如果仍舊對(duì)舊教程有興趣參考 Github 的markdown內(nèi)容[https://...
    tangyefei閱讀 35,184評(píng)論 22 257
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)页眯,斷路器梯捕,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • GitHub 上有一個(gè) Awesome - XXX 系列的資源整理,資源非常豐富,涉及面非常廣窝撵。awesome-p...
    若與閱讀 18,650評(píng)論 4 418
  • 人生下來(lái)不易傀顾, 人生則不易。 從一張白紙碌奉, 到五彩斑斕短曾。 遇到很多人和事, 遇到很多不解赐劣, 和無(wú)奈嫉拐。 如何平常心態(tài)...
    無(wú)敵小破壞閱讀 215評(píng)論 0 0