一、修饰器的起缘
编写测试某个函数时,往往需要给这个函数添加某些额外的功能。比方说下面这个sort_test函数:
import random
def sort_test():
orin_list=[random.randrange(0,100) for i in range(0,20)]
sorted_list=sorted(orin_list)
print(f"排序前:{orin_list}")
print(f"排序后:{sorted_list}")
sort_test()
如果我想测试这个sort_test函数运行时长,你可以在这个函数中添加测试语句,如下:
import random
from time import time
def sort_test():
t1=time()
orin_list=[random.randrange(0,100) for i in range(0,20)]
sorted_list=sorted(orin_list)
print(f"排序前:{orin_list}")
print(f"排序后:{sorted_list}")
t2=time()
print(f"耗时:{t2-t1}")
sort_test()
排序前:[14, 77, 34, 11, 20, 37, 97, 84, 92, 48, 63, 52, 68, 80, 69, 53, 93, 46, 98, 48]
排序后:[11, 14, 20, 34, 37, 46, 48, 48, 52, 53, 63, 68, 69, 77, 80, 84, 92, 93, 97, 98]
耗时:0.0
这样操作的确能够获知函数的运行时长,达到我们的目的。但这个操作无疑破坏了被测试的函数。这显然不符合面向对象编程的习惯。
所以,修饰器和修饰函数应运而生。
我们可以如下编写代码操作:
import random
from time import time
def runtime_of_sorttest(func):
def warp():
t1=time()
func()
t2=time()
print(t2-t1)
return warp
@runtime_of_sorttest
def sort_test():
orin_list=[random.randrange(0,100) for i in range(0,20)]
sorted_list=sorted(orin_list)
print(f"排序前:{orin_list}")
print(f"排序后:{sorted_list}")
sort_test()
排序前:[52, 54, 36, 27, 63, 37, 24, 15, 80, 74, 42, 33, 65, 30, 33, 75, 91, 49, 23, 59]
排序后:[15, 23, 24, 27, 30, 33, 33, 36, 37, 42, 49, 52, 54, 59, 63, 65, 74, 75, 80, 91]
0.0
此般操作,相当于不直接执行sort_test函数,而是把sort_test函数作为一个参数传入修饰函数runtime_of_sorttest,然后由修饰函数及其嵌套函数决定其是否执行,何时执行。
具体的操作方法是,在sort_test函数定义前加上@runtime_of_sorttest(也就是@修饰函数名称)
二、修饰函数的规范
下面来介绍这个修饰函数的规范和具体的使用方法。
def name_of_decorate(func):
def warp():
statement
func()
statement
return warp
修饰函数至少包含一层嵌套,对于上面这个最简单的情况,修饰函数有一层嵌套。第一层函数即为修饰函数;第二层函数用于代替原函数执行,习惯上命名为warp。
最后,修饰函数需要返回warp,也就是第二层函数
三、更为复杂的修饰函数
显而易见,上面这个修饰函数仅仅适用于被修饰函数没有参数,且没有返回值的情况。那么被修饰函数有返回值,或被修饰函数有参数,抑或是被修饰函数既有返回值,又有参数的情况,该如何处理呢?下面对多种情况进行介绍:
1、无返回值、无参数
也即我们上面已经讨论的情况,规范如下:
def name_of_decorate(func):
def warp():
statement
func()
statement
return warp
2、无返回值、有参数
修饰函数warp函数这一层的参数列表会接收原函数的参数。规范如下:
def name_of_decorate(func):
def warp(param1,param2):
statement
func(param1,param2)
statement
return warp
在warp处接收参数,也需要传入func()中执行!
具体例子如下,比方说,我想控制sort_test函数排序的列表长度,我可以以参数的形式传入。
import random
from time import time
def runtime_of_sorttest(func):
def warp(param_len):
t1=time()
func(param_len)
t2=time()
print(t2-t1)
return warp
@runtime_of_sorttest
def sort_test(length:int):
orin_list=[random.randrange(0,100) for i in range(0,length)]
sorted_list=sorted(orin_list)
print(f"排序前:{orin_list}")
print(f"排序后:{sorted_list}")
sort_test(5)
排序前:[24, 10, 11, 41, 44]
排序后:[10, 11, 24, 41, 44]
0.0
从结果来看,我们的目的达成了!
3、有返回值、无参数
warp函数的返回值会代替被修饰函数的返回值。
def name_of_decorate(func):
def warp():
statement
r=func()
statement
return r
return warp
具体的,对于sort_test函数,如果它有返回值——列表长度。那么修饰函数应该如下设计:
import random
from time import time
def runtime_of_sorttest(func):
def warp():
t1=time()
r=func()
t2=time()
print(t2-t1)
return r
return warp
@runtime_of_sorttest
def sort_test():
orin_list=[random.randrange(0,100) for i in range(0,20)]
sorted_list=sorted(orin_list)
print(f"排序前:{orin_list}")
print(f"排序后:{sorted_list}")
return len(sorted_list)
print(sort_test())
排序前:[94, 74, 54, 3, 68, 46, 25, 44, 90, 30, 48, 43, 45, 1, 59, 80, 39, 59, 12, 46]
排序后:[1, 3, 12, 25, 30, 39, 43, 44, 45, 46, 46, 48, 54, 59, 59, 68, 74, 80, 90, 94]
0.0
20
4、有返回值、有参数
把2、3两种情况组合一下就行了:
def name_of_decorate(func):
def warp(param1):
statement
r=func(param1)
statement
return r
return warp
具体样例如下:
import random
from time import time
def runtime_of_sorttest(func):
def warp(param_len):
t1=time()
r=func(param_len)
t2=time()
print(t2-t1)
return r
return warp
@runtime_of_sorttest
def sort_test(length:int):
orin_list=[random.randrange(0,100) for i in range(0,length)]
sorted_list=sorted(orin_list)
print(f"排序前:{orin_list}")
print(f"排序后:{sorted_list}")
return len(sorted_list)
print(sort_test(10))
有趣的是,wrap函数参数列表非常智能,你可以写成*args和**kwargs的形式:
import random
from time import time
def runtime_of_sorttest(func):
def warp(*args,**kwargs):
t1=time()
print(f"param:{args} and {kwargs}")
r=func(*args,**kwargs)
t2=time()
print(t2-t1)
return r
return warp
@runtime_of_sorttest
def sort_test(length:int):
orin_list=[random.randrange(0,100) for i in range(0,length)]
sorted_list=sorted(orin_list)
print(f"排序前:{orin_list}")
print(f"排序后:{sorted_list}")
return len(sorted_list)
print(sort_test(10))
param:(10,) and {}
排序前:[18, 33, 63, 36, 8, 84, 51, 55, 42, 18]
排序后:[8, 18, 18, 33, 36, 42, 51, 55, 63, 84]
0.0
10
你可以通过*args,**kwargs传参,也可以把对应的元组和字典打印出来!
5、修饰函数本身有参数
对于这个情况,就要使用两层嵌套函数了,也就是说,一共有三层函数!
规范如下:
def name_of_decorate(param):
def middle(func):
def warp(*args,**kwargs):
statement
r=func(param)
statement
return r
return warp
return middle
在这种情况下,warp函数变为了第三层函数,作用还是接收参数;而第二层函数接替修饰函数起到传递函数的作用,而修饰函数本身传递其自己的参数。
下面,我想用修饰函数来控制sort_test函数的排序列表长度,我可以这么做:
import random
from time import time
def runtime_of_sorttest(length_d:int):
def middle(func):
def wrap(*argc,**kwargs):
t1=time()
r=func(length_d)
t2=time()
print("execute time:%f"%(t2-t1))
print(f"original parameters: {argc} {kwargs}")
return r
return wrap
return middle
@runtime_of_sorttest(10)
def sort_test(length:int):
orin_list=[random.randrange(0,100) for i in range(0,length)]
sorted_list=sorted(orin_list)
print(f"排序前:{orin_list}")
print(f"排序后:{sorted_list}")
return len(sorted_list)
sort_test(20)
排序前:[43, 99, 50, 40, 97, 42, 34, 70, 25, 76]
排序后:[25, 34, 40, 42, 43, 50, 70, 76, 97, 99]
execute time:0.000000
original parameters: (20,) {}
这很有趣,因为我在sort_test()处传递的参数是20,列表长度应该是20;但由于修饰参数传参为10,且func()恰恰用了这个参数,所以排序的列表长度变成了10!
事实上,原始的数据20也是被接收了,结果第四行我已经打印了参数列表的元组,说明原始参数也被接收了,只是我们没有使用它!
四、多层嵌套的修饰函数
在编程时,还会使用多次嵌套的修饰函数。但我们把它当作一个洋葱,由外至内地剥开,再由内至外地合上即可。
这么说肯定难以理解,看下面的具体例子:
def fun_d1(func):
def warp():
print("fun_d1 start")
func()
print("fun_d1 stop")
return warp
def fun_d2(func):
def warp():
print("fun_d2 start")
func()
print("fun_d2 stop")
return warp
@fun_d1
@fun_d2
def fun():
print("fun execute")
fun()
fun_d1 start
fun_d2 start
fun execute
fun_d2 stop
fun_d1 stop
可以这样理解,@fun_d1修饰的是@fun_d2和fun函数组成的整体,先运行fun_d1中的wrap,直至发现func(),然后开始运行@fun_d2修饰函数。所以结果的最外层是fun_d1 start和fun_d1 stop。而里面的结果,是下面这个整体产生的:
@fun_d2
def fun():
print("fun execute")
也就是说,fun_d1中的func参数,代表是@fun_d2和fun函数组成的整体,而fun_d2中的fun参数,代表的是fun啊哈纳树本身!
五、一种似是而非的理解
def fun_d(func):
def warp():
print("fun_d execute")
func()
return warp
@fun_d
def fun():
print("fun execute")
fun()
有人把修饰函数理解为这样:
def fun_d(func):
def warp():
print("fun_d execute")
func()
return warp
def fun():
print("fun execute")
fun_d(fun())
也就是说修饰函数等价于这个嵌套:
fun_d(fun())
但事实上,你可以这么类比,但不能这样等价:
如果你尝试运行上述两端代码,得出的结果是不同的!!!
fun_d execute
fun execute
fun execute
而且,按照这样理解,应该先执行fun,再执行fun_d。但事实并非如此。如上,先打印了fun_d,再打印了fun。