装饰器定义
装饰器的主要功能是在不改变原有函数代码,且保持原函数调用方法不变的情况下,给原函数增加新的功能(或者给类增加属性和方法)
**核心思想**:用一个函数(或者类)去装饰一个旧函数(或者类),造出一个新函数(或者新类)
**应用场景**:引入日志,函数执行时间的统计,执行函数前的准备工作,执行函数后的处理工作,权限校验,缓存等
**语法规则**:在原有的函数上加上 @符,装饰器会把下面的函数当作参数传递到装饰器中,@符又被成为 语法糖
首先看这样一段代码
主要功能
def foo(): for i in range(10): print(i) foo()
添加需求 打印日志
现在需要在不改变这段代码前提下,计算出这段代码的运行时间日志
import time def log(fun): print(‘开始时间:%s‘ %(time.time())) fun() print(‘结束时间:%s‘ %(time.time())) def foo(): for i in range(10): print(i) log(foo)
优化调用方式 处理多次调用
我们将foo函数当成一个参数传递给了log(),在def log(fun)函数中,fun()就等同于foo(),好,现在可以显示运行的时间日志了,但是这样就改变了调用方式 需要使用log(foo)来调用,假设代码中使用了100次foo()现在需要打印每个foo()的运行日志,全部这样改是不现实的,我们需要优化一下 。
import time def log(fun): def run_log(): print(‘开始时间:%s‘ %(time.time())) fun() print(‘结束时间:%s‘ %(time.time())) return run_log def foo(): for i in range(10): print(i) foo = log(foo) foo()
现在我们改造了下代码,foo = log(foo)时,调用log()方法,内部定义了一个run_log()方法,然后返回给了foo,此时foo等于run_log,调用foo相当于调用run_log 这样,即实现了需要打印日志的需求,又可以不用去修改已有代码 。
传参
现在的foo()方法可以打印0到9数字,但是,由于业务需求变更,现在需要给foo()方法传递一个值,例如foo(100),就需要打印出0-99的数字,同时还要打印日志,所以我们需要再次优化代码
import time def log(fun): def run_log(num): print(‘开始时间:%s‘ %(time.time())) fun(num) print(‘结束时间:%s‘ %(time.time())) return run_log def foo(num): for i in range(num): print(i) foo = log(foo) foo(100)
根据之前的逻辑,我们已经知道新的foo()等于run_log(),所以我们给foo(100)传递100的值时,实际上等同于run_log(100),所以我们直接在def foo(num)中是接受不到的,我们需要在run_log(num)中接受到参数,传给fun(num),这样新的函数就可以接受参数了
返回参数
新的需求又来了,需要在foo()中返回一个所有数字的累加之和,而我们现有的foo函数实际上是run_log,所以我们再来改造一下代码
import time def log(fun): def run_log(num): print(‘开始时间:%s‘ % (time.time())) info = fun(num) print(‘结束时间:%s‘ % (time.time())) return info return run_log def foo(num): add_num = 0 for i in range(num): add_num += i print(i) return add_num foo = log(foo) x = foo(100) print(x)
由于现在的foo()等同于run_log(),run_log()中的fun()相当于foo(),所以foo中返回的值传到了info中然后我们把info返回,x就可以接受到从run_log中传递出来的参数
好了,到这里我们就实现了一个阉割版修饰器
通用性
假如需要你再给一个新的函数foo2打印日志, 代码如下
import time def log(fun): def run_log(num): print(‘开始时间:%s‘ % (time.time())) info = fun(num) print(‘结束时间:%s‘ % (time.time())) return info return run_log def foo(num): add_num = 0 for i in range(num): add_num += i print(i) return add_num def foo2(num, num2): add_num = 0 for i in range(num, num2): add_num += i print(i) return add_num foo = log(foo) x = foo(100) print(x)
由于我们的修饰器只能接收一个参数,而foo2需要两个参数,现有代码无法实现,所以我们要继续升级代码
import time def log(fun): def run_log(*args,**kwargs): print(‘开始时间:%s‘ % (time.time())) info = fun(*args,**kwargs) print(‘结束时间:%s‘ % (time.time())) return info return run_log def foo(num): add_num = 0 for i in range(num): add_num += i print(i) return add_num def foo2(num, num2): add_num = 0 for i in range(num, num2): add_num += i print(i) return add_num foo = log(foo) foo2 = log(foo2) x = foo(100) x2 = foo2(50,100) print(x) print(x2)
我们使用了(*args,**kwargs)这样就可以接收任意数量的参数,可以满足我们的所有需求
可变参数 *args
原理:python解释器会把传入的一组参数组装成一个tuple传递给可变参数。
关键字参数 **kwargs
原理:python解释器会把传入的一组参数组装成一个dict传递给关键字参数。
语法糖
但是每次使用前都要写一个 foo = log(foo)这样的赋值操作,代码并不美观 不符合python的代码风格,所以python给提供了一种语法糖,可以用@log来替代,修改后代码如下
mport time def log(fun): def run_log(*args,**kwargs): print(‘开始时间:%s‘ % (time.time())) info = fun(*args,**kwargs) print(‘结束时间:%s‘ % (time.time())) return info return run_log @log def foo(num): add_num = 0 for i in range(num): add_num += i print(i) return add_num @log def foo2(num, num2): add_num = 0 for i in range(num, num2): add_num += i print(i) return add_num x = foo(100) x2 = foo2(50, 100) print(x) print(x2)
我们只需要在定义函数时,在上面添加一句@修饰器名就相当于完成了函数名 = 修饰器名(函数名)这样的操作
现在就已经是一个标准的修饰器了
我们也可以简写一个标准的修饰器
def demo(func): def wrapper(*args,**kwargs): #函数增加的代码编写 res=func(*args,**kwargs) return res return wrapper