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~