1. 引子
1.1 实现函数执行时间的计算
1)被装饰函数不带参数的情况
a)执行timmer(func) 实现
代码如下
import time
def timmer(funcname):
start = time.time()
funcname()
print(time.time()-start)
def func1():
num = 0
for i in range(100000):
num += i
def func2():
num = 0
for i in range(10000000):
num +=1
timmer(func1) # 0.007220029830932617
timmer(func2) # 0.6535038948059082
上述代码,分别将func1的函数名
作为参数
传给timmer函数,而timmer函数在执行传入的func1函数前会先对执行func1的时间点做记录,待函数执行完之后,再由当前时间减去执行开始时的时间,计算得出函数func1执行耗费的时间(func2同理)
如果在现实情况中,func1函数可能会被调用1000+次,那么我就需要写1000+次的timmer(func1)来调用func1函数;甚是麻烦
那么我是否能够在不改变func1的调用方式的前提下,同时添加上函数执行时间计算的功能呢?
b)不改变被装饰函数的调用方式下实现
**关键点:**1.修饰器返回inner函数;2.func=timmer(func)
代码如下
import time
def timmer(funcname):
def inner():
start = time.time()
funcname()
print(time.time() - start)
return inner
def func1():
sum_num = 0
for i in range(100000):
sum_num += i
print('sum_num:',sum_num)
func1 = timmer(func1)
func1()
'''
输出:
sum_num: 4999950000
0.00814199447631836
'''
从上面代码可以看到,通过func1()的方法调用了func1函数,函数调用方式未发生改变,但是却实现了计算执行该函数所耗费的时间的功能
代码执行逻辑:
从上述执行逻辑中我们可以知道:
1)第3步,将上述定义的func1函数名作为参数传入了timmer函数,此时funcname即func1;而inner函数里的funcname()即func1()
2)从第6步开始,func1函数就已经重新赋值为inner函数,执行func1即执行timmer内部的inner函数,而第3步已经将func1函数传入到了inner函数中,那么我们又可以根据inner函数的具体内容可以知道,在执行原func1函数前记录时间点,执行之后计算所用时间
上述func1函数不带参数,那么如果func1带参数带化该怎么办呢?
2)被装饰函数带参数的情况
关键点: inner函数接收参数n,同时传入内部的funcname函数,funcname函数也接受参数n
代码如下:
import time
def timmer(funcname):
def inner(n): # 此处接收参数
start = time.time()
funcname(n) # 同时此处也要接收参数
print(time.time() - start)
return inner
def func1(n):
sum_num = 0
for i in range(n):
sum_num += i
print('sum_num:',sum_num)
func1 = timmer(func1)
func1(100000)
'''
输出:
sum_num: 4999950000
0.00752711296081543
'''
代码执行逻辑:
从上述描述我们得出:
关键在于,最后执行func1即执行inner,参数最终需要传递给funcname函数,就需要先传递给inner函数,然后再有funcname函数接收
接下来又有这样一个问题,现在有一个func2函数,不需要传参数,但是也想计算函数执行时间,现在该如何?
一个函数带参数,一个函数不带参数,均要计算函数执行时间,该如何?
3)传参数量不同的函数被修饰的情况
**关键点:**应用*args
和 **kwargs
代码1如下:
import time
def timmer(funcname):
def inner(n): # 此处接收参数
start = time.time()
funcname(n) # 同时此处也要接收参数
print(time.time() - start)
return inner
def func1(n):
sum_num = 0
for i in range(n):
sum_num += i
print('sum_num_f1:',sum_num)
def func2():
sum_num = 0
for i in range(1000000):
sum_num += i
print('sum_num_f2',sum_num)
上述代码,timmer函数里的inner函数是带参数的,而func2不带参数,那么如果直接func2 = timmer(func2)
肯定会报错,该如何是好?
这个时候就需要应用我们的*args
和 **kwargs
传位置参数与关键字参数的知识了
修改后的代码2如下:
import time
def timmer(funcname):
def inner(*args,**kwargs):
start = time.time()
funcname(*args,**kwargs)
print(time.time() - start)
return inner
def func1(n):
sum_num = 0
for i in range(n):
sum_num += i
print('sum_num_f1:',sum_num)
def func2():
sum_num = 0
for i in range(1000000):
sum_num += i
print('sum_num_f2',sum_num)
func1 = timmer(func1)
func2 = timmer(func2)
func1(100000)
func2()
'''
输出:
sum_num_f1: 4999950000
0.007891178131103516
sum_num_f2 499999500000
0.06601905822753906
'''
此时,无论要计算执行时间的函数不带参数、带位置参数、关键字参数,均可通过func=timmer(func)
的方法来将func传入timmer,进而进行执行时间的计算
我们说过,要尽量保持函数执行方式不变的情况下来计算其执行时间,但是上述所有代码中,实际上在调用函数之前都是执行了func = timmer(func)
的;如何在不执行该语句的情况下,直接执行函数而达到计算函数时间执行时间的功能呢?
1.2 @timmer
代替 func=timmer(func)
关键点:func=timmer(func)
可以不写在调用函数的位置,可以提前直接写在定义函数的位置,语法是:@timmer
代码如下:
import time
def timmer(funcname):
def inner(*args,**kwargs):
start = time.time()
funcname(*args,**kwargs)
print(time.time() - start)
return inner
@timmer # 相当于 func1 = timmer(func1)
def func1(n):
sum_num = 0
for i in range(n):
sum_num += i
print('sum_num_f1:',sum_num)
@timmer # 相当于 func2 = timmer(func2)
def func2():
sum_num = 0
for i in range(1000000):
sum_num += i
print('sum_num_f2',sum_num)
func1(100000)
func2()
'''
输出:
sum_num_f1: 4999950000
0.006793975830078125
sum_num_f2 499999500000
0.06452298164367676
'''
目前即可直接在执行func1或者func2函数时加载上计算时间的功能了。
目前还有个小问题需要调整一下。上述代码中func1和func2函数均用的print函数来查看结果,但在实际情况中通常用return来返回函数执行的结果。稍作修改即可
完善代码,通过return返回结果:
import time
def timmer(funcname):
def inner(*args,**kwargs):
start = time.time()
ret = funcname(*args,**kwargs) # ret接收funcname的返回结果
print(time.time() - start)
return ret # 返回ret
return inner
@timmer # 相当于 func1 = timmer(func1)
def func1(n):
sum_num = 0
for i in range(n):
sum_num += i
return sum_num # return返回结果
@timmer # 相当于 func2 = timmer(func2)
def func2():
sum_num = 0
for i in range(1000000):
sum_num += i
return sum_num # return返回结果
ret1 = func1(100000)
ret2 = func2()
print('ret1:',ret1)
print('ret2:',ret2)
'''
输出:
0.0065839290618896484
0.06833600997924805
ret1: 4999950000
ret2: 499999500000
'''
2. 什么情况下用装饰器
1)在已经写好发版的程序的基础上,需要对一个函数执行前后增加功能的时候
开放封闭原则:
开放:对扩展是开放的
封闭:对修改是封闭的
即保证了程序/函数的可扩展性,由保证了已写好的函数不被任意修改
2)有的时候也会写好一些装饰器,加载需要装饰的函数上面
如:对每一个动作(函数)加载一个日志记录的装饰器,这样就能实时记录用户操作日志
3. 装饰器的固定格式
3.1 固定格式
# 定义装饰器
def 装饰器名(func):
def inner(*args,**kwargs):
'''执行被装饰的函数之前要做的事'''
ret = func(*args,**kwargs)
'''执行被装饰的函数之后要做的事'''
return ret
return inner
# 用装饰器装饰test函数
@装饰器名
def test():
pass
# 执行test函数
test()
3.2 示例
import time
def wrapper(func):
def inner(*args,**kwargs):
ret = func(*args,**kwargs)
t = time.strftime('%Y-%m-%d %H:%M:%S')
print('%s时刻[%s]执行了%s函数' % (t,args[0],func.__name__))
return ret
return inner
@wrapper
def login(name):
print('%s登录了...' % name)
login('tom')
'''
输出:
tom登录了...
2019-11-18 00:26:00时刻[tom]执行了login函数
'''
如上述,就可以在执行login函数之后,输出具体的执行日志;如果进一步将日志写入日志文件,即可实现最基础的日志记录功能