装饰器概念
装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限验证等场景,装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
装饰器代码实战
友情提示:接下来的代码有点多,希望大家可以拷贝下来,实际运行一下,相信会对装饰器这个概念有更为深刻的理解!
给大家举个例子,定义一个now函数 输出当前时间
def now ():
print(time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()))
现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print('call start:' +func.__name__)
func(*args, **kw)
print('call end:' + func.__name__)
return wrapper
这是一个较为固定的写法:
参数func表示传入的函数对象
wrapper是内部函数,return wrapper 会实现对其调用
(*args, **kw)是一种固定用法,表示可以传入任意的参数,*args和**kw分别属于非关键字参数和关键字参数,两者也都是可变参数。一个星号*加上参数名,比如*number,定义后,number可以接收任意数量的参数,并将它们储存在一个tuple中。关键字参数的特征是两个星号**加上参数名,比如**kw, 定义后,kw将接收到的任意数量参数存到一个dict中。举个例子就懂了
def func_para(*args, **kw):
print ('args:',args )
print ('kw:',kw )
func_para(1,2,3,4, a=1,b=2,c=3)
输出:
args: (1, 2, 3, 4)
kw: {'a': 1, 'b': 2, 'c': 3}
func(*args, **kw) 表示对传入的函数进行调用,调用前后分别执行了两条print语句
func.__name__ 表示函数的名字
def wrapper 根据需求也可以return 某个值。
最后调用装饰器的代码如下:在now上面加上装饰器 @log即可
def func_para(*args, **kw):
print ('args:',args )
print ('kw:',kw )
func_para(1,2,3,4, a=1,b=2,c=3)
@log
def now ():
print(time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()))
now()
输出:
call start:now
2023-03-10-15_08_40
call end:now
看到这里有的同学可能会问,如果now()函数需要增加参数怎么办?很简单,我们无须对装饰器log进行任何修改,代码如下:
@log
def now (a,b):
print(a)
print(time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()))
print(b)
now("test1","test2")
输出:
call startnow
test1
2023-03-10-15_08_40
test2
call end:now
装饰器的本质
其实想要了解装饰器的本质,我们需要了解python的函数对象!python中一切皆是对象,所以函数也不例外,我们还是以下面代码为例
def now ():
print(time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()))
print(now)
print(type(now))
输出:
<function now at 0x000001C7C5D44F78>
<class 'function'>
可以看到输出了now对象的地址和对应的类型。我们也可以理解函数的名字就是函数在内存中对应的地址
我们可以把函数赋值:
a=now
print(a)
此时输出的a 的值与print(now) 是一样的!
我们也可以把函数作为参数传递,例如
import time
def now():
print(time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()))
def func_demo(func):
return func #调用作为参数传入的函数
func1=func_demo(now)
func1()
输出:
2023-03-10-15_18_44
讲到这里,我们可以看出来
@log
def now ():
其实等价于
log(now)
这里now作为了装饰器函数log的参数,@log只是语法糖而已,语法糖是计算机语言中特殊的某种语法,这种语法对语言的功能并没有影响,对于程序员有更好的易用性,能够增加程序的可读性。大家可以结合前面讲解的def log函数的代码,然后执行以下代码
import time
def log(func):
def wrapper(*args, **kw):
print('call start:' + func.__name__)
func(*args, **kw)
print('call end:' + func.__name__)
return wrapper
@log
def now ():
print(time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()))
d=log(now)
d()
会输出:
call start:wrapper
call start:now
2023-03-10-15_29_47
call end:now
call end:wrapper