本文總結自oldboy python教學視頻。 一、前言 1.裝飾器本質上就是函數,功能是裝飾其他函數,為其他函數添加附加功能。 裝飾器在裝飾函數時必須遵循3個重要的原則: (1)不能修改被裝飾的函數的源代碼 (2)不能修改被裝飾的函數的調用方式 (3)不能修改表裝飾函數的返回結果 2.實現裝飾器的 ...
本文總結自oldboy python教學視頻。
一、前言
1.裝飾器本質上就是函數,功能是裝飾其他函數,為其他函數添加附加功能。
裝飾器在裝飾函數時必須遵循3個重要的原則:
(1)不能修改被裝飾的函數的源代碼
(2)不能修改被裝飾的函數的調用方式
(3)不能修改表裝飾函數的返回結果
2.實現裝飾器的知識儲備:
(1)函數即“變數”
關於函數即變數,我的理解如下所示,即當定義一個函數時,相當於在記憶體中劃一塊記憶體空間,裡面存放函數的函數體,而函數名指向這塊空間——這跟普通的函數定義如 a = 9527 是一樣的。如此一來,函數名便可以跟普通變數一樣,作為參數傳給其他函數,也能在函數中作為結果被返回。
(2)高階函數
a.把一個函數名當做實參傳給另一個函數(在不修改被裝飾函數源代碼的情況下為其添加功能)
或:
b.返回值中包含函數名(不修改函數的調用方式)
(3)嵌套函數
所謂嵌套函數,即在一個函數內定義另外一個函數(註意是定義,而不是調用)。
3.高階函數 + 嵌套函數 => 裝飾器
二、裝飾器應用
1.被裝飾函數沒有參數
假設被裝飾的函數為test1(),此時的test1()沒有形參,預設返回NULL;裝飾器timmer完成的附加功能是計時。
# -*- coding:utf-8 -*- #Author:Suxy import time def timmer(func): def wrapper(): start_time = time.time() func() stop_time = time.time() print("the func run time is %s" %(stop_time - start_time)) return wrapper @timmer #test1 = timmer(test1),即test1 = wrapper def test1(): time.sleep(1) print("in the test1") test1()
輸出:
C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py
in the test1
the func run time is 1.0003414154052734
Process finished with exit code 0
註:在函數test1()的上面加上@timmer,作用相當於test1 = timmer(test1),又因為timmer函數中返回wrapper,所以有test1 = timmer(test1) = wrapper,因此最後一行代碼在使用test()方式調用函數的時候,實際調用的是wrapper(),而wrapper中的變數func接收了timemr(test1)傳進來的test1,所以實際調用時,先執行wrapper中附加的功能(計時),之後執行test1()本身的功能。
2.被裝飾函數帶有參數
裝飾器不僅要能裝飾無參函數,還得能裝飾帶參函數。這裡以只帶一個參數的函數test2(name)為例。此時若是不修改上面裝飾器的任何代碼便用來裝飾test2(name),則會報錯,具體如下:
# -*- coding:utf-8 -*- #Author:Suxy import time def timmer(func): def wrapper(): start_time = time.time() func() stop_time = time.time() print("the func run time is %s" %(stop_time - start_time)) return wrapper @timmer #test1 = timmer(test1),即test1 = wrapper def test1(): time.sleep(1) print("in the test1") @timmer def test2(name): time.sleep(1) print("in the test2, ", name) test1() test2("suxy")
輸出:
C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py Traceback (most recent call last): File "E:/python_workspace/pure_python/day4/decorator.py", line 25, in <module> test2("suxy") TypeError: wrapper() takes 0 positional arguments but 1 was given in the test1 the func run time is 1.000788688659668 Process finished with exit code 1
可見,此時裝飾器timmer用於裝飾test1()時沒有問題,但是裝飾test2()時報錯了:wrapper()函數接受0個位置參數,但是在調用時卻傳了1個。
由第1步可知,在使用裝飾器timmer裝飾函數test2之後,調用test2(“suxy”)就相當於調用wrapper(“suxy”)函數,而當前的wrapper()函數是不接收參數的。
若需要timme裝飾器能夠同時裝飾test1和test2函數,則需要對timmer裝飾器進行修改——不過也不能簡單地將timmer()中wrapper()函數的定義修改為wrapper(name),因為這樣一來timmer裝飾test1就會出錯,此外,timmer裝飾器有可能還要裝飾其他的比如帶有預設參數、關鍵字參數等的函數,因此,wrapper函數的定義應修改為wrapper(*args, **kwargs),並且wrapper中func的調用方式也得進行相應的修改func(*args, **kwargs),這樣一來,無論被裝飾的函數是無參函數、預設參數、關鍵字參數還是如*args列表參數、**kwargs字典參數,裝飾器都能進行裝飾。如下:
# -*- coding:utf-8 -*- #Author:Suxy import time def timmer(func): def wrapper(*args, **kwargs): start_time = time.time() func(*args, **kwargs) stop_time = time.time() print("the func run time is %s" %(stop_time - start_time)) return wrapper @timmer #test1 = timmer(test1),即test1 = wrapper def test1(): time.sleep(1) print("in the test1") @timmer def test2(name): time.sleep(1) print("\nin the test2, ", name) test1() test2("suxy")
輸出:
C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py in the test1 the func run time is 1.000464916229248 in the test2, suxy the func run time is 1.0004689693450928 Process finished with exit code 0
3.上面第2步所說的裝飾器已經能夠裝飾一般的帶參函數了,不過,若是被裝飾函數具有返回值,則上面的裝飾器就有問題了。如下(為縮減篇幅,暫將函數test1省略掉):
# -*- coding:utf-8 -*- #Author:Suxy import time def timmer(func): def wrapper(*args, **kwargs): start_time = time.time() func(*args, **kwargs) stop_time = time.time() print("the func run time is %s" %(stop_time - start_time)) return wrapper @timmer def test2(name): time.sleep(1) print("\nin the test2, ", name) return 9527 print(test2("suxy"))
輸出:
C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py in the test2, suxy the func run time is 1.0003151893615723 None Process finished with exit code 0
可見,函數test2()中明明返回了9527,但是最後輸出確是None,違反了裝飾器不能修改被裝飾函數的返回結果的原則。這是因為裝飾器的wrapper中調用func(*args, **kwargs)函數(註: func(*args, **kwargs) = test2(*args, **kwargs))時,只是進行直接的調用,而沒有將調用的結果返回。可以將裝飾器代碼修改為 return func(*args, **kwargs),或者先用res = func(*args, **kwargs)將函數執行結果保存到res中,最後再返回res。如下:
# -*- coding:utf-8 -*- #Author:Suxy import time def timmer(func): def wrapper(*args, **kwargs): start_time = time.time() res = func(*args, **kwargs) stop_time = time.time() print("the func run time is %s" %(stop_time - start_time)) return res return wrapper @timmer def test2(name): time.sleep(1) print("\nin the test2, ", name) return 9527 print(test2("suxy"))
輸出:
C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py in the test2, suxy the func run time is 1.0006110668182373 9527 Process finished with exit code 0
4.到上一步,裝飾器已經可以裝飾絕大部分的函數了。但是目前的裝飾器只能簡單的接受一個參數,即被裝飾函數的函數名,無法接受其他的參數。若是有其他複雜的需求,比如接受func_type欄位,根據func_type的不同而做不同的輸出,則目前的裝飾器無法完成這個功能。
# -*- coding:utf-8 -*- #Author:Suxy import time def before_timmer(func_type): if func_type == "type1": print("It is type 1") elif func_type == "type2": print("It is type 2") else: print("Another type") def timmer(func): def wrapper(*args, **kwargs): start_time = time.time() res = func(*args, **kwargs) stop_time = time.time() print("the func run time is %s" % (stop_time - start_time)) return res return wrapper return timmer @before_timmer(func_type = "type2") def test2(name): time.sleep(1) print("\nin the test2, ", name) return 9527 test2("suxy")
輸出:
C:\app\Anaconda3\python.exe E:/python_workspace/pure_python/day4/decorator.py It is type 2 in the test2, suxy the func run time is 1.0008769035339355 Process finished with exit code 0
仔細對比這裡和3中裝飾器代碼的區別可以發現,這一步相當於又新建了一個裝飾器before_timmer將原先的裝飾器timmer包了起來,並將timmer函數作為結果返回,使得可以在before_timmer裝飾器中先完成判斷功能,再調用timmer計時功能。
此時對於test1()函數定義上的@before_timmer(func_type = "type1"),我的理解是:test2 = before_timmer(func_type = “type2”),又因為before_timmer中有return timmer,所以test2 = before_timmer(func_type = “type2”) = timmer(test2),同時由於timmer中有return wrapper,所以 test2 = before_timmer(func_type = “type2”) = timmer(test2) = wrapper,因此,當調用test2(“suxy”)時,還是最後還是相當於調用了wrapper(“suxy”)。