注意:
如果對閉包不了解的同學(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)