? ? ? ?筆者之前寫過一篇簡單介紹python函數裝飾器用法的文章,本文便不再贅述。簡而言之,pythong函數裝飾器是一種通過特定語法,給函數額外增加一層邏輯,以實現相應目的的一種語法結構。本文將介紹裝飾器的原理、被裝飾函數是如何以參數的形式傳入到裝飾器函數內部的,以及實現給裝飾器函數設定參數的參數傳遞原理。
def decorator(func):
def wrapper(*args,**kargs):
print('I love Python!')
func()
return wrapper
@decorator
def fun():
print('test')
? ? ? ?如上代碼,是一個典型的關于函數裝飾器的例子,但遺憾的是,這個例子其實說明不了太多的東西,僅僅只是介紹了函數裝飾器的最簡單的一種應用方式而已。為了理解函數裝飾器,我們從最簡單最直接的裝飾器構造方式,一步步展開。
def decorator(func):
print('just for testing')
return func
@decorator
def fun():
print('test')
? ? ? ?上面的代碼實際上也可以順利執行,當腳本運行到@語句時,便會把函數fun以參數的形式傳給decorator,然后裝飾器函數decorator便會運行,運行的結果就是執行print,然后將fun返回給fun自己,因為這時裝飾器函數返回的函數就是fun本身,所以當我們再調用fun的時候,fun的行為和其定義的一樣,沒有任何變化。通過這個例子我們知道,實際上函數裝飾器的語法就是:通過@聲明裝飾器函數,被裝飾函數作為參數傳給裝飾器函數,然后裝飾器函數會執行,執行的最后需要返回一個函數給被裝飾函數,即被裝飾的函數重綁定到裝飾器函數返回函數。換言之,@語句會直接執行裝飾器函數,然后返回一個函數給被裝飾器函數,而這個裝飾器函數內部的執行過程是任意的,并不一定需要定義wrapper函數,就像上面的代碼所示。
? ? ? ?當然,上面的代碼僅僅是為了說明裝飾器的原理,一般并不會這么使用。一般情況下,為了給函數添加額外的邏輯層,我們會在裝飾器函數內定義一個wrapper函數,在其內部實現額外的邏輯,然后將wrapper函數返回以重新綁定到原函數。更細致的說, 裝飾器函數自己必須是個可調用的函數或者返回一個可調用函數,我們說這個可直接調用或者返回的可直接調用的函數才是真正的裝飾器,真正的裝飾器函數是單參的,參數就是被裝飾函數 。這里說的裝飾器函數也可以是一個返回一個可調用函數的函數,這樣的機制可以讓我們實現給裝飾器函數設定參數,當然這時的裝飾器函數實際上已經不是真正的裝飾器了,真正的裝飾器是其返回的可調用函數,對此后面我們細講。
? ? ? ?然后講一下關于被裝飾函數的參數的傳遞。如果被裝飾函數有參數,嚴格的講,是如果我們調用被裝飾函數時,我們給被裝飾函數傳遞了參數,那么這個參數是如何傳給裝飾器函數內部的呢?看如下代碼。
def decorator_1(func):
def wrapper(*args,**kargs):
func()
return wrapper
@decorator_1
def fun_1(x,y):
print(x+y)
fun_1(3,4) # output: TypeError: fun() missing 2 required positional arguments: 'x' and 'y'
def decorator_2(func):
def wrapper(*args,**kargs):
func(*args,**kargs)
return wrapper
@decorator_2
def fun_2(x,y):
print(x+y)
fun_2(3,4) # output: 7
? ? ? ?如上代碼所示,如果我們要給被裝飾函數傳遞參數,那么其就應該按照第二種寫法實現。因為實際上,被裝飾函數的參數會自動傳給裝飾器函數中的被返回的函數,所以我們應該給wrapper函數設定*args,**args的形式,以保證fun_2被裝飾函數的任何參數形式其都能接收,此外,wrapper函數中的func函數的參數形式也應該同樣設定,就是為了可以接收任何函數形式,包括位置參數和關鍵字參數。
? ? ? ?最后我們將講解如何給裝飾器函數設定參數。先看參考代碼。
def outer(number):
def decorator_1(func):
def wrapper(*args,**kargs):
print('I love Python!')
func(*args,**kargs)
return wrapper
def decorator_2(func):
def wrapper(*args,**kargs):
print('I love Pandas!')
func(*args,**kargs)
return wrapper
if number==0:
return decorator_1
else:
return decorator_2
@outer(0)
def fun1():
print('test')
@outer(1)
def fun2():
print('test')
? ? ? ?因為裝飾器語法有如下的等價形式:
@decorator
def fun():
? ? pass
等價于
fun=decorator(fun)
即這里的decorator必須是一個可調用函數,但是如果其是一個可執行函數,那么其返回的結果必須是一個可調用函數,這時,有如下等價關系:
@decorator(args)
def fun():
? ? pass
等價于
fun=decorator(args)(fun)
因為decorator(args)本身就是一個可直接執行的函數,那么其必須返回一個可調用函數,假設為deco,則上面進一步等價為fun=deco(fun),所以這時,實際上deco才是真正的裝飾器函數。
? ? ? ?下面就好理解上面的代碼了。因為outer(0)、outer(1)都是直接可執行的函數,其執行后返回的結果分別為decorator_1、decorator_2,所以實際上有如下等價關系:
@outer(0)
def fun1():
? ? print('test')
@outer(1)
def fun2():
? ? print('test')
等價于
@decorator_1
def fun1():
? ? print('test')
@decorator_2
def fun2():
? ? print('test')
這樣,就不難理解,為何被裝飾函數可以作為參數傳遞給outer函數內部的decorator函數,所以,這時其實outer內部的decorator函數才是真正的裝飾器函數。
? ? ? ?最后總結一下本文講的內容:
1、@decorator語法和@deco(a,b)語句,相當于直接執行decorator(fun)和deco(a,b)(fun),并把執行結果返回給fun;
2、被裝飾函數的參數會自行的直接傳給真正的裝飾器函數內的被返回的可調用函數,而被返回的可調用函數最好通過*args,**kargs的形式設定參數,以接收任何的參數形式;
3、在理解1的基礎上,通過外加一層函數,以實現給裝飾器函數自定義參數。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
