装饰器是一个非常好用的功能,能使程序更加简洁,同时也减少代码量。那什么是装饰器呢?文字描述会比较苍白,还是举例子,大家更加容易接受。目前装饰器比较认同的应用有程序性能,插入日志等功能。
程序性能
有这么一个需求,要求输出每个函数的执行时长,以此来规避一些程序性能问题。
第一版
import time
def func_1():
time.sleep(1)
for i in range(10000):
if i < 10000:
continue
return 1
start = time.time()
func_1()
end = time.time()
print("elapse_time:", end-start)
上述代码是最简单的实现函数func_1耗时的案例。
若要统计十个函数,那是不是要重复十遍 主程序呢?好吧,优化一下下:
第二版
import time
def func_1():
time.sleep(1)
for i in range(10000):
if i < 10000:
continue
return 1
def elapse_time(func):
start = time.time()
func()
end = time.time()
print("elapse_time:", end-start)
elapse_time(func_1)
看起来简单多了,即使有多个函数需要统计性能,那么也只要多行调用代码即可:
第三版
import time
def func_1():
time.sleep(1)
for i in range(20000):
if i < 20000:
continue
return 1
def func_2():
time.sleep(1)
for i in range(30000):
if i < 30000:
continue
return 1
def func_3():
time.sleep(1)
for i in range(10000):
if i < 10000:
continue
return 1
def elapse_time(func):
start = time.time()
func()
end = time.time()
print("elapse_time:", end-start)
elapse_time(func_1)
elapse_time(func_2)
elapse_time(func_3)
那么问题来了,如果有更多的函数,如果要统计更多的函数的程序性能的话,上述方法看起来就不太够用了。有什么更好的方法呢?就是接下去要说的装饰器了(在需处理的函数定义前使用语法糖@+函数名)。
第四版
import time
def elapse_time(func):
start = time.time()
func()
end = time.time()
print("elapse_time:", end-start)
@elapse_time
def func_1():
time.sleep(1)
for i in range(20000):
if i < 20000:
continue
return 1
@elapse_time
def func_2():
time.sleep(1)
for i in range(30000):
if i < 30000:
continue
return 1
@elapse_time
def func_3():
time.sleep(1)
for i in range(10000):
if i < 10000:
continue
return 1
func_1
func_2
func_3
第四版调用函数的时没有括号总觉得怪怪的,可读性不是很高,所以需要调整下:
第五版
import time
def elapse_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print("elapse_time:", end-start)
return wrapper
@elapse_time
def func_1():
time.sleep(1)
for i in range(20000):
if i < 20000:
continue
return 1
@elapse_time
def func_2():
time.sleep(1)
for i in range(30000):
if i < 30000:
continue
return 1
@elapse_time
def func_3():
time.sleep(1)
for i in range(10000):
if i < 10000:
continue
return 1
func_1()
func_2()
func_3()
第五版与第三版的代码量其实没有多大差别。当被修饰的函数(func_1,func_2,func_3)多次调用时,第三版每次调用前都要用上 elapse_time()函数,而第五版不需要去关心这个,不管调用多少次都会统计每次的程序执行时间。此外,也不需要在调用被修饰函数时,做任何处理,减轻了代码的维护成本。第五版看似已经满足要求了,但是通常情况下,我们执行的fun_1,fun_2,fun_3都会带参数,那么如何处理呢?
第六版
import time
def elapse_time(func):
def wrapper(*args):
start = time.time()
func(*args)
end = time.time()
print("elapse_time:", end-start)
return wrapper
@elapse_time
def func_1(time_sleep,range_num):
time.sleep(time_sleep)
for i in range(range_num):
if i < range_num:
continue
return 1
func_1(1,200000)
定义一个可变参数*args,如果有必要还可以定义**kwargs
插入日志
上述介绍了一个简单的案例,接下来看看插入日志功能如何实现:
第一版
import logging
def insert_log(func):
def wrapper(*args):
logging.info("Function {0} is running".format(func.__name__))
return func(*args)
return wrapper
@insert_log
def func():
print("I am func")
logging.basicConfig(filename="demo1.log",filemode="a",level="DEBUG")
func()
So Easy ,不难嘛。加大一点难度,插入日志的装饰器需要带参数(插入日志前,需判断日志是否需要打印):
第二版
import logging
def insert_log(is_print=False):
def deractor(func):
def wrapper(*args):
if is_print:
print("Function {0} is running".format(func.__name__))
logging.info("Function {0} is running".format(func.__name__))
return func(*args)
return wrapper
return deractor
@insert_log(is_print=True)
def func():
print("I am func")
logging.basicConfig(filename="demo1.log",filemode="a",level="DEBUG")
func()
实现起来也没有那么复杂,其实就是在最外层再包一层即可。在修饰时,可带上参数。
如果既要打印日志,又要统计函数运行时间应该如何处理呢?
第三版
import time
import logging
def elapse_time(func):
def wrapper(*args):
start = time.time()
func(*args)
end = time.time()
print("elapse_time:", end-start)
return wrapper
def insert_log(is_print=False):
def deractor(func):
def wrapper(*args):
if is_print:
print("Function {0} is running".format(func.__name__))
logging.info("Function {0} is running".format(func.__name__))
return func(*args)
return wrapper
return deractor
@elapse_time
def func_1(time_sleep,range_num):
time.sleep(time_sleep)
for i in range(range_num):
if i < range_num:
continue
return 1
@elapse_time
@insert_log(is_print=True)
def func():
print("I am func")
logging.basicConfig(filename="demo1.log",filemode="a",level="DEBUG")
func()
其实使用多个装饰器即可。需要注意的是,下层的装饰器执行悠闲于上层的装饰器。上述代码:
@elapse_time
@insert_log(is_print=True)
def func():
print("I am func")
程序运行时,@insert_log(is_print=True)装饰器先执行,后执行@elapse_time装饰器。
运行结果如下:
D:\software\Python3.4.3\Install\python3.exe D:/scripts/git/others/demo1.py
Function func is running
I am func
elapse_time: 0.003000020980834961
Process finished with exit code 0