您好,登錄后才能下訂單哦!
Python裝飾器(decorator)是在程序開(kāi)發(fā)中經(jīng)常使用到的功能,合理使用裝飾器,能讓我們的程序如虎添翼。
裝飾器引入
初期及問(wèn)題誕生
假如現(xiàn)在在一個(gè)公司,有A B C三個(gè)業(yè)務(wù)部門,還有S一個(gè)基礎(chǔ)服務(wù)部門,目前呢,S部門提供了兩個(gè)函數(shù),供其他部門調(diào)用,函數(shù)如下:
def f1(): print('f1 called') def f2(): print('f2 called')
在初期,其他部門這樣調(diào)用是沒(méi)有問(wèn)題的,隨著公司業(yè)務(wù)的發(fā)展,現(xiàn)在S部門需要對(duì)函數(shù)調(diào)用假如權(quán)限驗(yàn)證,如果有權(quán)限的話,才能進(jìn)行調(diào)用,否則調(diào)用失敗??紤]一下,如果是我們,該怎么做呢?
方案集合
問(wèn)題
那么,有沒(méi)有一種方法能夠遵循代碼的開(kāi)放閉合原則,來(lái)完美的解決此問(wèn)題呢?
裝飾器引入
答案肯定是有的,不然真的是弱爆了。先看代碼
def w1(func): def inner(): print('...驗(yàn)證權(quán)限...') func() return inner @w1 def f1(): print('f1 called') @w1 def f2(): print('f2 called') f1() f2()
輸出結(jié)果為
...驗(yàn)證權(quán)限...
f1 called
...驗(yàn)證權(quán)限...
f2 called
可以通過(guò)代碼及輸出看到,在調(diào)用f1 f2 函數(shù)時(shí),成功進(jìn)行了權(quán)限驗(yàn)證,那么是怎么做到的呢?其實(shí)這里就使用到了裝飾器,通過(guò)定義一個(gè)閉包函數(shù)w1,在我們調(diào)用函數(shù)上通過(guò)關(guān)鍵詞@w1,這樣就對(duì)f1 f2函數(shù)完成了裝飾。
裝飾器原理
首先,開(kāi)看我們的裝飾器函數(shù)w1,該函數(shù)接收一個(gè)參數(shù)func,其實(shí)就是接收一個(gè)方法名,w1內(nèi)部又定義一個(gè)函數(shù)inner,在inner函數(shù)中增加權(quán)限校驗(yàn),并在驗(yàn)證完權(quán)限后調(diào)用傳進(jìn)來(lái)的參數(shù)func,同時(shí)w1的返回值為內(nèi)部函數(shù)inner,其實(shí)就是一個(gè)閉包函數(shù)。
然后,再來(lái)看一下,在f1上增加@w1,那這是什么意思呢?當(dāng)python解釋器執(zhí)行到這句話的時(shí)候,會(huì)去調(diào)用w1函數(shù),同時(shí)將被裝飾的函數(shù)名作為參數(shù)傳入(此時(shí)為f1),根據(jù)閉包一文分析,在執(zhí)行w1函數(shù)的時(shí)候,此時(shí)直接把inner函數(shù)返回了,同時(shí)把它賦值給f1,此時(shí)的f1已經(jīng)不是未加裝飾時(shí)的f1了,而是指向了w1.inner函數(shù)地址。
接下來(lái),在調(diào)用f1()的時(shí)候,其實(shí)調(diào)用的是w1.inner函數(shù),那么此時(shí)就會(huì)先執(zhí)行權(quán)限驗(yàn)證,然后再調(diào)用原來(lái)的f1(),該處的f1就是通過(guò)裝飾傳進(jìn)來(lái)的參數(shù)f1。
這樣下來(lái),就完成了對(duì)f1的裝飾,實(shí)現(xiàn)了權(quán)限驗(yàn)證。
裝飾器知識(shí)點(diǎn)
執(zhí)行時(shí)機(jī)
了解了裝飾器的原理后,那么它的執(zhí)行時(shí)機(jī)是什么樣呢,接下來(lái)就來(lái)看一下。
國(guó)際慣例,先上代碼
def w1(fun): print('...裝飾器開(kāi)始裝飾...') def inner(): print('...驗(yàn)證權(quán)限...') fun() return inner @w1 def test(): print('test') test()
輸出結(jié)果為
...裝飾器開(kāi)始裝飾...
...驗(yàn)證權(quán)限...
test
由此可以發(fā)現(xiàn),當(dāng)python解釋器執(zhí)行到@w1時(shí),就開(kāi)始進(jìn)行裝飾了,相當(dāng)于執(zhí)行了如下代碼:
test = w1(test)
兩個(gè)裝飾器執(zhí)行流程和裝飾結(jié)果
當(dāng)有兩個(gè)或兩個(gè)以上裝飾器裝飾一個(gè)函數(shù)時(shí),那么執(zhí)行流程和裝飾結(jié)果是什么樣的呢?同樣,還是以代碼來(lái)說(shuō)明問(wèn)題。
def makeBold(fun): print('----a----') def inner(): print('----1----') return '<b>' + fun() + '</b>' return inner def makeItalic(fun): print('----b----') def inner(): print('----2----') return '<i>' + fun() + '</i>' return inner @makeBold @makeItalic def test(): print('----c----') print('----3----') return 'hello python decorator' ret = test() print(ret)
輸出結(jié)果:
----b----
----a----
----1----
----2----
----c----
----3----
<b><i>hello python decorator</i></b>
可以發(fā)現(xiàn),先用第二個(gè)裝飾器(makeItalic)進(jìn)行裝飾,接著再用第一個(gè)裝飾器(makeBold)進(jìn)行裝飾,而在調(diào)用過(guò)程中,先執(zhí)行第一個(gè)裝飾器(makeBold),接著再執(zhí)行第二個(gè)裝飾器(makeItalic)。
為什么呢,分兩步來(lái)分析一下。
對(duì)無(wú)參函數(shù)進(jìn)行裝飾
上面例子中的f1 f2都是對(duì)無(wú)參函數(shù)的裝飾,不再單獨(dú)舉例
對(duì)有參函數(shù)進(jìn)行裝飾
在使用中,有的函數(shù)可能會(huì)帶有參數(shù),那么這種如何處理呢?
代碼優(yōu)先:
def w_say(fun): """ 如果原函數(shù)有參數(shù),那閉包函數(shù)必須保持參數(shù)個(gè)數(shù)一致,并且將參數(shù)傳遞給原方法 """ def inner(name): """ 如果被裝飾的函數(shù)有行參,那么閉包函數(shù)必須有參數(shù) :param name: :return: """ print('say inner called') fun(name) return inner @w_say def hello(name): print('hello ' + name) hello('wangcai')
輸出結(jié)果為:
say inner called
hello wangcai
具體說(shuō)明代碼注釋已經(jīng)有了,就不再單獨(dú)說(shuō)明了。
此時(shí),也許你就會(huì)問(wèn)了,那是一個(gè)參數(shù)的,如果多個(gè)或者不定長(zhǎng)參數(shù)呢,該如何處理呢?看看下面的代碼你就秒懂了。
def w_add(func): def inner(*args, **kwargs): print('add inner called') func(*args, **kwargs) return inner @w_add def add(a, b): print('%d + %d = %d' % (a, b, a + b)) @w_add def add2(a, b, c): print('%d + %d + %d = %d' % (a, b, c, a + b + c)) add(2, 4) add2(2, 4, 6)
輸出結(jié)果為:
add inner called
2 + 4 = 6
add inner called
2 + 4 + 6 = 12
利用python的可變參數(shù)輕松實(shí)現(xiàn)裝飾帶參數(shù)的函數(shù)。
對(duì)帶返回值的函數(shù)進(jìn)行裝飾
下面對(duì)有返回值的函數(shù)進(jìn)行裝飾,按照之前的寫(xiě)法,代碼是這樣的
def w_test(func): def inner(): print('w_test inner called start') func() print('w_test inner called end') return inner @w_test def test(): print('this is test fun') return 'hello' ret = test() print('ret value is %s' % ret)
輸出結(jié)果為:
w_test inner called start
this is test fun
w_test inner called end
ret value is None
可以發(fā)現(xiàn),此時(shí),并沒(méi)有輸出test函數(shù)的‘hello',而是None,那是為什么呢,可以發(fā)現(xiàn),在inner函數(shù)中對(duì)test進(jìn)行了調(diào)用,但是沒(méi)有接受不了返回值,也沒(méi)有進(jìn)行返回,那么默認(rèn)就是None了,知道了原因,那么來(lái)修改一下代碼:
def w_test(func): def inner(): print('w_test inner called start') str = func() print('w_test inner called end') return str return inner @w_test def test(): print('this is test fun') return 'hello' ret = test() print('ret value is %s' % ret)
輸出結(jié)果:
w_test inner called start
this is test fun
w_test inner called end
ret value is hello
這樣就達(dá)到預(yù)期,完成對(duì)帶返回值參數(shù)的函數(shù)進(jìn)行裝飾。
帶參數(shù)的裝飾器
介紹了對(duì)帶參數(shù)的函數(shù)和有返回值的函數(shù)進(jìn)行裝飾,那么有沒(méi)有帶參數(shù)的裝飾器呢,如果有的話,又有什么用呢?
答案肯定是有的,接下來(lái)通過(guò)代碼來(lái)看一下吧。
def func_args(pre='xiaoqiang'): def w_test_log(func): def inner(): print('...記錄日志...visitor is %s' % pre) func() return inner return w_test_log # 帶有參數(shù)的裝飾器能夠起到在運(yùn)行時(shí),有不同的功能 # 先執(zhí)行func_args('wangcai'),返回w_test_log函數(shù)的引用 # @w_test_log # 使用@w_test_log對(duì)test_log進(jìn)行裝飾 @func_args('wangcai') def test_log(): print('this is test log') test_log()
輸出結(jié)果為:
...記錄日志...visitor is wangcai
this is test log
簡(jiǎn)單理解,帶參數(shù)的裝飾器就是在原閉包的基礎(chǔ)上又加了一層閉包,通過(guò)外層函數(shù)func_args的返回值w_test_log就看出來(lái)了,具體執(zhí)行流程在注釋里已經(jīng)說(shuō)明了。
好處就是可以在運(yùn)行時(shí),針對(duì)不同的參數(shù)做不同的應(yīng)用功能處理。
通用裝飾器
介紹了這么多,在實(shí)際應(yīng)用中,如果針對(duì)沒(méi)個(gè)類別的函數(shù)都要寫(xiě)一個(gè)裝飾器的話,估計(jì)就累死了,那么有沒(méi)有通用萬(wàn)能裝飾器呢,答案肯定是有的,廢話不多說(shuō),直接上代碼。
def w_test(func): def inner(*args, **kwargs): ret = func(*args, **kwargs) return ret return inner @w_test def test(): print('test called') @w_test def test1(): print('test1 called') return 'python' @w_test def test2(a): print('test2 called and value is %d ' % a) test() test1() test2(9)
輸出結(jié)果為:
test called
test1 called
test2 called and value is 9
把上面幾種示例結(jié)合起來(lái),就完成了通用裝飾器的功能,原理都同上,就不過(guò)多廢話了。
類裝飾器
裝飾器函數(shù)其實(shí)是一個(gè)接口約束,它必須接受一個(gè)callable對(duì)象作為參數(shù),然后返回一個(gè)callable對(duì)象。
在python中,一般callable對(duì)象都是函數(shù),但是也有例外。比如只要某個(gè)對(duì)象重寫(xiě)了call方法,那么這個(gè)對(duì)象就是callable的。
當(dāng)創(chuàng)建一個(gè)對(duì)象后,直接去執(zhí)行這個(gè)對(duì)象,那么是會(huì)拋出異常的,因?yàn)樗皇莄allable,無(wú)法直接執(zhí)行,但進(jìn)行修改后,就可以直接執(zhí)行調(diào)用了,如下
class Test(object): def __call__(self, *args, **kwargs): print('call called') t = Test() print(t())
輸出為:
call called
下面,引入正題,看一下如何用類裝飾函數(shù)。
class Test(object): def __init__(self, func): print('test init') print('func name is %s ' % func.__name__) self.__func = func def __call__(self, *args, **kwargs): print('裝飾器中的功能') self.__func() @Test def test(): print('this is test func') test()
輸出結(jié)果為:
test init
func name is test
裝飾器中的功能
this is test func
和之前的原理一樣,當(dāng)python解釋器執(zhí)行到到@Test時(shí),會(huì)把當(dāng)前test函數(shù)作為參數(shù)傳入Test對(duì)象,調(diào)用init方法,同時(shí)將test函數(shù)指向創(chuàng)建的Test對(duì)象,那么在接下來(lái)執(zhí)行test()的時(shí)候,其實(shí)就是直接對(duì)創(chuàng)建的對(duì)象進(jìn)行調(diào)用,執(zhí)行其call方法。
好了,到目前為止,基本把python裝飾器及相關(guān)知識(shí)點(diǎn)講完了,如有問(wèn)題,歡迎指出哈~
以上所述是小編給大家介紹的python裝飾器簡(jiǎn)介詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)億速云網(wǎng)站的支持!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。