从小例子进入装饰器
统计一个函数执行耗时
-
原始版本
import time # time模块有提供时间相关函数 def do_something(): print("do_something") time.sleep(0.5) # 让程序停止0.5秒模拟其它操作耗时 start = time.time() do_something() print(time.time() - start) #result: # do_something # 0.5000283718109131
问题:上述代码可以完成这个功能,但之后会发现,如果我们要统计其它函数,就必须在每个函数前后加入相应代码
-
装饰器版本1(无参数)
import time def execute_time(func): def inner(): start = time.time() func() print(time.time() - start) return inner # time模块有提供时间相关函数 def do_something(): print("do_something") time.sleep(0.5) # 让程序停止0.5秒模拟其它操作耗时 do_something = execute_time(do_something) do_something() #result: # do_something # 0.5000283718109131
从上述代码可以看到,使用了另一个函数execute_time()给我们要统计耗时的函数进行了包装,这时,这个execute_time()函数就叫做装饰器函数,而我们要统计的那个函数也就是do_something()函数就是被装饰的函数.问题:函数执行的时候实际上是调用的execute_time()函数中的inner()函数,这种方法虽然解决了原始版本的问题,但是当我们要统计的函数拥有返回值的时候,这时候我们获取不到返回值.
-
装饰器版本2(有固定参数)
import time def execute_time(func): def inner(do): start = time.time() result = func(do) print(time.time() - start) return result return inner # time模块有提供时间相关函数 def do_something(do): print("do_something", do) time.sleep(0.5) # 让程序停止0.5秒模拟其它操作耗时 return 'do_something over' do_something = execute_time(do_something) print(do_something('say hello')) # result: # do_something say hello # 0.5000283718109131 # do_something over
为了解决装饰器版本1的问题,我在inner()函数里面加了个返回值.问题:当被装饰函数的参数个数与inner()参数个数不同时,这个装饰器就不适用了
-
装饰器版本3(动态参数)
import time def execute_time(func): def inner(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(time.time() - start) return result return inner # time模块有提供时间相关函数 def do_something(do1,do2): print("do_something", do1,do2) time.sleep(0.5) # 让程序停止0.5秒模拟其它操作耗时 return 'do_something over' do_something = execute_time(do_something) print(do_something('say hello1','say hello2')) # result: # do_something say hello1 say hello2 # 0.5000283718109131 # do_something over
在第七天内容中有个知识点是动态参数,刚好可以解决这个问题
-
终极版本(语法糖@)
import time def execute_time(func): def inner(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(time.time() - start) return result return inner @execute_time def do_something(do1,do2): print("do_something", do1,do2) time.sleep(0.5) # 让程序停止0.5秒模拟其它操作耗时 return 'do_something over' # do_something = execute_time(do_something) print(do_something('say hello1','say hello2')) # result: # do_something say hello1 say hello2 # 0.5000283718109131 # do_something over
对于装饰器,python内部给我们提供了语法糖支持.在需要被装饰的函数名上部使用[@装饰器函数名称]即可,简化上述代码18行
装饰器进阶
获取被包装函数原生属性
-
例1:常规函数取函数名
def func(): print('执行中') print(func.__name__) func() # result: # 执行中 # func
常规函数可以通过函数的__name__属性可拿到当前函数名称
-
例2:被装饰函数取函数名
def wrapper(func): def inner(): print('执行前') result = func() print('执行后') return result return inner; @wrapper def func(): print('执行中') print(func.__name__) func() # result: # 执行前 # 执行中 # inner # 执行后
问题:通过执行结果会发现,结果中想获取的函数名是func,而实际结果是inner.原因是@wrapper进行包装相当于执行一个操作:func=wrapper(func)=inner
-
解决
使用functools模块
from functools import wraps def wrapper(func): @wraps(func) def inner(): print('执行前') result = func() print('执行后') return result return inner; @wrapper def func(): print('执行中') print(func.__name__) func() # result: # 执行前 # 执行中 # func # 执行后
导入functools模块后通过第4行操作,会发现执行的函数即使被包装但还是能获取到它本身的属性
带参数的装饰器
-
例1
def wrapper(func): def inner(): print('执行前') result = func() print('执行后') return result return inner; @wrapper def func_1(): print('执行中') @wrapper def func_2(): print('执行中') @wrapper def func_3(): print('执行中') ... @wrapper def func_n(): print('执行中')
问题:通过上述代码会发现,有很多函数都用了同一个装饰器,如果有一天要取消这些函数上的装饰,就必须对每一个函数进行修改
-
解决
定义一个全局变量flag,并给原本装饰器外部再加一层函数用来接收参数,inner()函数内部通过外部参数flag判断被装饰函数的执行与否,修改flag即可控制装饰器的执行结果,如下:
from functools import wraps flag = True def wrapper_out(flag): def wrapper(func): @wraps(func) def inner(): if (flag): print('{}执行前'.format(func.__name__)) result = func() print('{}执行后'.format(func.__name__)) else: result = func() return result return inner return wrapper @wrapper_out(flag) def func_1(): print('{}执行中'.format(func_1.__name__)) @wrapper_out(flag) def func_2(): print('{}执行中'.format(func_2.__name__)) @wrapper_out(flag) def func_3(): print('{}执行中'.format(func_3.__name__)) ... @wrapper_out(flag) def func_n(): print('{}执行中'.format(func_n.__name__)) func_1() func_2() func_3() func_n() #result: # func_1执行前 # func_1执行中 # func_1执行后 # func_2执行前 # func_2执行中 # func_2执行后 # func_3执行前 # func_3执行中 # func_3执行后 # func_n执行前 # func_n执行中 # func_n执行后
from functools import wraps flag = False def wrapper_out(flag): def wrapper(func): @wraps(func) def inner(): if (flag): print('{}执行前'.format(func.__name__)) result = func() print('{}执行后'.format(func.__name__)) else: result = func() return result return inner return wrapper @wrapper_out(flag) def func_1(): print('{}执行中'.format(func_1.__name__)) @wrapper_out(flag) def func_2(): print('{}执行中'.format(func_2.__name__)) @wrapper_out(flag) def func_3(): print('{}执行中'.format(func_3.__name__)) ... @wrapper_out(flag) def func_n(): print('{}执行中'.format(func_n.__name__)) func_1() func_2() func_3() func_n() #result: # func_1执行中 # func_2执行中 # func_3执行中 # func_n执行中
多个装饰器装饰同一个函数
-
代码
def wrapper1(func): def inner(): print('wrapper1 ,before func') func() print('wrapper1 ,after func') return inner def wrapper2(func): def inner(): print('wrapper2 ,before func') func() print('wrapper2 ,after func') return inner @wrapper1 @wrapper2 def f(): print('in f') f() # result: # wrapper1 ,before func # wrapper2 ,before func # in f # wrapper2 ,after func # wrapper1 ,after func
-
图解
从上图可以看到,从1-9步是装饰器的装载过程,10-18步是执行过程.结论:多个装饰器装饰同一个函数时,装载顺序是从下到上,但执行顺序却是从上到下,可以理解为创建了一个装饰器栈(先进后出)