天天看點

python 帶參裝飾器_python帶參數的裝飾器

裝飾器更進階的用法是可以帶參數。

帶參數的裝飾器

先來看一個不帶參數的裝飾器

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__這樣的操作。