装饰器用于给已有函数在不改变已有函数的情况下额外增加功能
使用场景有增加统一日志、埋点、性能调优
简单装饰器的写法
def printlog(func):
def wrapper():
print("[DEBUG LOG]: enter {}".format(func.__name__))
return func()
return wrapper
@printlog
def saygoodbye():
print("good Bye")
if __name__ == '__main__':
saygoodbye()
print(saygoodbye.__name__)
--------------
打印结果
[DEBUG LOG]: enter saygoodbye
good Bye
wrapper
调用过程:
1.main函数调用了saygoodbye函数
2.saygoodbye 打上了printlog装饰器的标签,=>转去执行printlog
3.printlog接收一个函数,即将saygoodbye函数传入printlog内
4.printlog 直接return了wrapper,wrapper是一个内嵌函数=>转去执行wrapper
5.wrapper函数首先打印[DEBUG LOG]: enter (func.__name__),将saygoodbye函数名打印出来
6.wrapper函数 return func(),=>转去执行原函数saygoodbye
整个过程就是在执行saygoodbye函数前,首先去执行装饰器中的功能,最后return到原调用函数,实现了给原有函数增加功能
装饰器有两个必须条件,装饰器接收一个函数,并返回一个函数,需要使用装饰器的函数在函数上方使用@装饰器名去关联装饰器
装饰器接收的函数就是关联装饰器的函数,返回的函数是装饰器中内部定义的内嵌函数,这个内嵌函数最后会返回原调用函数。
拿上面例子代码解释一下:装饰器printlog接收的函数是saygoodbye,返回的函数是装饰器中定义的wrapper函数,wrapper函数返回的是原调用函数saygoodbye
但是最后一行print(saygoodbye.__name__),却返回的不是saygoodbye,而是装饰器中的内嵌函数wrapper,原来saygoodbye已经被printlog装饰器装饰成了wrapper函数,我们却希望得到的仍是saygoodbye这个原函数。
所以装饰器中的内嵌函数使用@functools.wraps(func)来消除这个问题
def printlog(func):
@functools.wraps(func)
def wrapper():
print("[DEBUG LOG]: enter {}".format(func.__name__))
return func()
return wrapper
@printlog
def saygoodbye():
print("good Bye")
if __name__ == '__main__':
saygoodbye()
print(saygoodbye.__name__)
-----------------
返回结果
[DEBUG LOG]: enter saygoodbye
good Bye
saygoodbye
可见,被装饰的函数因为有了@functools.wraps(func)没有被改变名称
进阶装饰器——函数传参
上一个举例中saygoodbye函数没有参数,那么如果是有参数的函数想要用装饰器怎么办?
我们在内嵌函数定义时将参数原封不动传入,然后return原函数时再传递给原函数
这里使用到可变传参、关键字传参,即使参数个数不同、或者无参数都可以正常调用
def printlog(func):
@functools.wraps(func)
def wrapper(*args,**kws):
print("[DEBUG LOG]: enter {}".format(func.__name__))
return func(*args,**kws)
return wrapper
装饰器内嵌函数使用(*args,**kws)将函数参数收入,然后又传入原调用函数func(*args,**kws),下面是调用举例
@printlog
def say(content='hello'):
print("say {}".format(content))
@printlog
def do(sb,sth):
print('{} is {}'.format(sb,sth))
@printlog
def doSthInWhere(sb,sth,where):
print('{} is {} in {}'.format(sb,sth,where))
@printlog
def introduce(**map):
print(map)
for key in map:
print('{} is {} years old'.format(key,map[key]))
if __name__ == '__main__':
say('hello')
do('Lily','sleep')
do(*['Lily', 'sleep'])
doSthInWhere('Lily','sleep','home')
stu = {'Tom':10,'Jack':11}
introduce(**stu)
可以看到,不论是几个传参个数,都可以成功使用装饰器的功能
装饰器再进阶——装饰器传参:
如果装饰器中的新增功能,想要根据装饰器的参数做对应的操作,如日志打印想根据传参打印日志级别,那么就需要在用装饰器的时候给他传入参数
实现办法:在上面装饰器的外面再包一层函数定义,函数传入装饰器的参数,然后在用装饰器的时候给他传入参数
def outputlog(level):
def printlog(func):
@functools.wraps(func)
def wrapper(*args,**kws):
print("[{level} LOG]: enter {funcname}".format(level=level,funcname=func.__name__))
return func(*args,**kws)
return wrapper
return printlog
@outputlog('INFO')
def say(content='hello'):
print("say {}".format(content))
if __name__ == '__main__':
say('hello')
--------------
打印结果:
[INFO LOG]: enter say
say hello
这样,就可以实现装饰器中传入参数
编写类装饰器
将装饰器函数改写为类装饰器,将装饰器的调用函数及装饰器的参数定义写在init中,内嵌函数写在___call__魔法方法里,这样看着代码可读性更好一些
定义没有装饰器参数的类装饰器
class outputlog(object):
#在init中赋值func给类装饰器
def __init__(self,func):
self.func= func
#在__call__中编写需要增加的功能代码,最后return 调用原函数
def __call__(self, *args, **kwargs):
print("[DEBUG LOG]: enter {func}".format( func=self.func.__name__))
return self.func(*args, **kwargs)
传入参数的类装饰器
class outputlog(object):
def __init__(self,level="INFO"):
self.level= level
def __call__(self,func):
@functools.wraps(func)
def innerfun(*args, **kwargs):
print("[{} LOG]: enter {func}".format( self.level,func=func.__name__))
# print("[{level} LOG]: enter {funcname}".format(level=self.level, funcname=self.func.__name__))
func(*args, **kwargs)
return innerfun
@outputlog()
def say(content='hello'):
print("say {}".format(content))
@outputlog('INFO')
def do(sb,sth):
print('{} is {}'.format(sb,sth))
if __name__ == '__main__':
say()
say('hello')
----------------
结果:
[INFO LOG]: enter say
say hello
[INFO LOG]: enter say
say hello
在init中只传入类装饰器的参数,在__call__传入调用函数func,这时__call__相当于外面包的那层函数,里面的内嵌函数使用@functools.wraps(func)修饰,并在函数中调用func,不用写return