裝飾器更進階的用法是可以帶參數。
帶參數的裝飾器
先來看一個不帶參數的裝飾器
1
2
3
4
5
6
7
8
9
10
11
12
13
14import time
def timeit(fn):
def wrap(*args,**kwargs):
start = time.time()
ret = fn(*args,**kwargs)
print(time.time() - start)
return ret
return wrap
@timeit
def sleep(x):
time.sleep(x)
1sleep(3)
3.0034420490264893
這裡列印出來的是執行sleep函數所消耗的自然時間,但在執行此函數時所消耗的cpu時間真的有3.0034420490264893秒嗎?當然不是。利用time包中的time.clock方法可以計算得到代碼執行所消耗cpu的時間,那怎樣來修改上邊的timeit函數,讓其即能計算代碼執行的自然時間,也能計算代碼執行所消耗cpu的時間?做如下改進:
1
2
3
4
5
6
7
8
9
10
11
12
13def timeit_1(process_time=False):
cacl = time.clock if process_time else time.time
def timeit_2(fn):
def wrap(*args,**kwargs):
start = cacl()
ret = fn(*args,**kwargs)
print(cacl() - start)
return ret
return wrap
return timeit_2
def sleep_1(x):
time.sleep(x)
1timeit_1(True)(sleep_1)(3)
0.020000000000000018
1timeit_1(False)(sleep_1)(3)
3.0038363933563232
1timeit_1()(sleep_1)(3) # 參數process_time是一個預設參數,是以可以不傳遞值,預設為False
3.003509283065796
上邊的調用過程是怎樣的呢?分解一下,如下:
1fn1 = timeit_1(True)
上邊調用timeit_1(True),函數return回了timeit_2,并把fn1這個變量指向了調用結果,即指向了timeit_2,這裡的timeit_2也是一個函數,此函數接收一個參數
1fn2 = fn1(sleep_1)
這裡調用fn1(sleep_1),其實就是調用了timeit_2(sleep_1),并把fn2這個變量指向了調用後的結果,即指向了warp,這裡的warp也是一個函數,此函數能接收任意的參數
1fn2(3)
0.009999999999999787
上邊調用fn2(3),其實是調用了wrap(3),即執行了wrap函數内的語句,此函數内的ret = fn(*args,**kwargs)語句中的fn其實是指向了sleep,是以在執行wrap函數時,sleep_1函數才真正被執行。
既然裝飾器可以用魔法來裝飾一個函數,那上邊經過改進過的裝飾器是不是也能裝飾一個函數呢?如下:
1
2
[email protected]_1(False)
def sleep_2(x):
time.sleep(x)
1sleep_2(3)
3.0039477348327637
如果想計算代碼執行的cpu時間,那如下即可:
1
2
[email protected]_1(True)
def sleep_3(x):
time.sleep(x)
1sleep_3(3)
0.0
這個魔法又發生了什麼呢?
其實質就是在沒有用魔法的情況下直接timeit_1(True)(sleep_3)(3)。而當使用@這個魔法後,當代碼執行到此行時,解析器會執行timeit_1(True),timeit_1實質就是一函數,接收一個參數,并傳回一個timeit_2函數。當代碼執行到@所在語句時,會把所裝飾的sleep_3函數作為一個參數傳遞給timeit_1(True)的調用結果,即timeit_2這個函數,即sleep_3這個函數已作為一個變量傳遞給了timeit_2(fn)中的fn參數,并傳回了一個wrap函數,在接下的調用sleep_3(3)這個操作,其實此時的sleep_3這個函數已不是原先的def sleep_3(x):中的sleep_3函數,而是一個指向了wrap的函數,wrap函數接收任何參數,是以把當執行sleep_3(3)時,把參數3傳遞給了wrap函數,并執行内部的代碼,内部代碼中ret = fn(*args,**kwargs)中的fn函數依賴還是指向原先的sleep_3(x)這個函數。
這裡也有一個簡單的記憶方式,如果一個函數被裝飾器所裝飾,在調用這個函數時其實不再是調用表面上看上去的這個函數,以
1
2
[email protected]_1(True)
def sleep_3(x):
time.sleep(x)
來做說明。當執行到有@魔法所在行時,相當于執行了sleep_3 = timeit_1(True)(sleep_3),即指向了wrap函數,既然sleep_3指向了wrap函數,那我們執行sleep_3(3)時,其實就是在進行wrap(3)這樣的函數調用,記住,函數名也是一個變量。
再來舉一個帶參數的裝飾器的例子,比如有一個函數,隻有在對有許可權限的使用者開放,執行此函數的使用者沒有在認證清單裡的,就不會執行這個函數。這個該如何實作呢?如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14def check(allow_users):
def inner_check(fn):
def wrap(username,*args,**kwargs):
'''This is wrap'''
if username in allow_users:
return fn(username,*args,**kwargs)
return "You are illegal users"
return wrap
return inner_check
@check(['zhaochj','zcj'])
def private(username):
'''The authentication'''
return "You are legitimate users"
1private('zhaochj')
'You are legitimate users'
1private('tom')
'You are illegal users'
這樣就可以對調用函數進行身份驗證。
python中一個函數有一些屬性是函數本身具有的,比如__name__屬性是檢視函數名稱,__doc__是檢視函數文檔的等等。如果一個函數被裝飾器裝飾過後,這個函數的這些屬性會發生怎樣的變化呢?以上邊的check裝飾器和private函數為例子,如下:
1private.__name__
'wrap'
1private.__doc__
'This is wrap'
private函數的名稱是wrap了,文檔也是wrap函數的文檔,這是怎麼回事?上邊已經說過,這裡的private函數被裝飾器裝飾後它已不再是原來的private函數,private這個函數名稱會被指向到wrap這個函數對象,那當然用上邊的private.__name__和private.__doc__檢視函數的屬性就會是wrap函數的屬性。那怎樣來修正呢?可以這樣做,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def check_1(allow_users):
def inner_check(fn):
def wrap(username,*args,**kwargs):
'''This is wrap'''
if username in allow_users:
return fn(username,*args,**kwargs)
return "You are illegal users"
wrap.__name__ = fn.__name__
wrap.__doc__ = fn.__doc__
return wrap
return inner_check
@check_1(['zhaochj','zcj'])
def private_1(username):
'''The authentication'''
return "You are legitimate users"
1private_1.__name__
'private_1'
1private_1.__doc__
'The authentication'
通過在裝飾器把__name__和__doc__重新指派後就能更正這個問題,但對一個函數來說像__name__這樣類似的屬性有許多,如果都是這樣手工來修正顯然是不現實的,是以python提供了一個wraps裝飾器來自動修正這個問題,wraps在functools這個包中,是以可以這樣來修正這個問題,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import functools
def check_1(allow_users):
def inner_check(fn):
@functools.wraps(fn)
def wrap(username,*args,**kwargs):
'''This is wrap'''
if username in allow_users:
return fn(username,*args,**kwargs)
return "You are illegal users"
return wrap
return inner_check
@check_1(['zhaochj','zcj'])
def private_1(username):
'''The authentication'''
return "You are legitimate users"
1private_1.__name__
'private_1'
1private_1.__doc__
'The authentication'
@functools.wraps(fn)這個裝飾器相當于執行了wrap.__name__ = fn.__name__這樣的操作。