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