python的代码的特点:优雅,明确,简单,装饰器就是这些特点的很好体现。
一、装饰器的本质
python装饰器本质:func=wrapper(func) <==> @wrapper 两种等价的写法,可以看出decorator就是一个返回函数的高阶函数,decorator的作用在于它可以在不用更改原函数的情况下增加新的功能,为已经存在的函数或对象添加额外的功能,也可以抽离大量与函数功能本身无关的相似代码进行复用,常用于事物处理,插入日志等场景。
完整的装饰器写法如下:log装饰器,包含@语法糖,可用于任意目标函数
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
带参数的decorator如下:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
下面我们来看看装饰器到底是什么
1、要了解装饰器首先要理解,函数本身也是一个对象,函数对象也可以被赋值,所以函数也能通过变量来被调用。eg:
#coding:utf-8
import time
def func():
print time.strftime('%Y.%m.%d',time.localtime(time.time()))
if __name__=='__main__':
func()
f=func
f() #通过变量调用函数
print func.__name__
print f.__name__
2019.02.20
2019.02.20
func
func
2、设想func()函数本身不能满足功能要求,代码本身又不能做更改,比如上面的代码,笔者不止想让它打印日期,还想加上程序运行时间,这里就用到了装饰器。参照上面的标准写法,demo就是一个装饰器,包含@语法糖,增加了打印func()运行时间的功能
#coding:utf-8
import time
import functools
def demo(func):
def wrapper():
starttime=time.time()
func()
endtime=time.time()
ms=(endtime-starttime)*1000
print ("Running time: %d ms" %ms)
return wrapper
@demo
def func():
time.sleep(1)
print time.strftime('%Y.%m.%d',time.localtime(time.time()))
if __name__=='__main__':
func()
print func.__name__
2019.02.20
Running time: 1000 ms
wrapper
上述代码func函数被wrapper包装在里面,相当于func=demo(func),由函数结构看出demo(func)返回的值是wrapper,即func()=demo(func)()=wrapper()。
注意: 从结果看出函数对象的__name__属性变了,从func变成wrapper,因为demo函数中定义返回的就是wrapper
3、在实际应用过程中,函数是有参数的,而且参数个数类型不定,装饰器也能搞定
固定参数的装饰器示例代码如下:
#coding:utf-8
import time,functools
def demo(func):
def wrapper(a,b):
starttime=time.time()
func(a,b)
endtime=time.time()
ms=(endtime-starttime)*1000
print ("Running time: %d ms" %ms)
return wrapper
@demo
def func(a,b):
time.sleep(1)
print time.strftime('%Y.%m.%d',time.localtime(time.time()))
print a+b
if __name__=='__main__':
func(1,3)
print func.__name__
2019.02.20
4
Running time: 1000 ms
wrapper
参数不定的装饰器(可用于任意目标函数)示例代码如下:
可变参数*args和关键字参数**kw知识点传送门—https://blog.youkuaiyun.com/weixin_43533825/article/details/87517242
#coding:utf-8
import time,functools
def demo(func):
def wrapper(*args,**kw):
starttime=time.time()
func(*args,**kw)
endtime=time.time()
ms=(endtime-starttime)*1000
print ("Running time: %d ms" %ms)
return wrapper
@demo
def func(a,b):
time.sleep(1)
print time.strftime('%Y.%m.%d',time.localtime(time.time()))
print a+b
@demo
def functest(a,b,c=2):
time.sleep(1)
print time.strftime('%Y.%m.%d',time.localtime(time.time()))
print a+b+c
if __name__=='__main__':
func(1,3)
functest(1,2,c=10)
print func.__name__
2019.02.20
4
Running time: 1000 ms
2019.02.20
13
Running time: 1000 ms
wrapper
上面提到函数被“装饰”之后,__name__属性就变了,这样会导致依赖函数签名的代码执行报错,python内置的functools.wraps解决了这个问题,示例代码如下:可以看到函数的__name__属性还是func。
#coding:utf-8
import time,functools
def demo(func):
@functools.wraps(func)
def wrapper(*args,**kw):
starttime=time.time()
func(*args,**kw)
endtime=time.time()
ms=(endtime-starttime)*1000
print ("Running time: %d ms" %ms)
return wrapper
@demo
def func(a,b):
time.sleep(1)
print time.strftime('%Y.%m.%d',time.localtime(time.time()))
print a+b
@demo
def functest(a,b,c=2):
time.sleep(1)
print time.strftime('%Y.%m.%d',time.localtime(time.time()))
print a+b+c
if __name__=='__main__':
func(1,3)
functest(1,2,c=10)
print func.__name__
2019.02.20
4
Running time: 1000 ms
2019.02.20
13
Running time: 1000 ms
func
二、多装饰器
原函数需要增加多个功能,因此可能需要多个装饰器,示例代码如下:
多个装饰器执行顺序:由近及远。
#coding:utf-8
import time,functools
def demo1(func):
print "demo1 run"
def wrapper(*args,**kw):
print "demo1 before func"
func(*args,**kw)
print "demo1 after func"
return wrapper
def demo2(func):
print "demo2 run"
def wrapper(*args,**kw):
print "demo2 before func"
func(*args,**kw)
print "demo2 after func"
return wrapper
@demo1
@demo2
def func(a,b):
time.sleep(1)
print time.strftime('%Y.%m.%d',time.localtime(time.time()))
print a+b
if __name__=='__main__':
func(1,3)
print func.__name__
demo2 run
demo1 run
demo1 before func
demo2 before func
2019.02.20
4
demo2 after func
demo1 after func
wrapper
三、带参数的装饰器
实际应用场景中,decorator本身也会需要传入参数(eg:@log会需要传入日志级别),实际上是三层嵌套,示例代码如下:
#coding:utf-8
import time,functools
def log(level):
def demo(func):
@functools.wraps(func)
def wrapper(*args,**kw):
starttime=time.time()
print level
func(*args,**kw)
endtime=time.time()
ms=(endtime-starttime)*1000
print ("Running time: %d ms" %ms)
return wrapper
return demo
@log('Info')
def func(a,b):
time.sleep(1)
print time.strftime('%Y.%m.%d',time.localtime(time.time()))
print a+b
if __name__=='__main__':
func(1,3)
print func.__name__
Info
2019.02.21
4
Running time: 1002 ms
func
和上面的func=demo(func)相比,这里相当于func=log(level)(func)
即func()=log(level='Info')(func)=demo(level)(func)()=wrapper(level)()。
四、类装饰器
由前面的装饰器本质可知,如果类对象要实现装饰器,它必须是可调用的;而__call__()这个方法,可以将类的实例变成可调用对象,通过__init__()传入函数,重载__call__()函数,达到装饰器的目的,示例代码如下:
class log(object):
def __init__(self, func):
self.func = func
def __call__(self,*args,**kw):
print("decorator start")
self.func(*args,**kw)
print("decorator end")
@log
def func(a,b,c=10):
print a+b+c
func(1,2,5)
decorator start
8
decorator end
带参数的类装饰器,__init__()传入的不是函数,而是参数,__call__()接受一个函数,并返回一个函数,示例代码如下:
class log(object):
def __init__(self, level='info'):
self.level = level
def __call__(self,func):
def wrapper(*args,**kw):
print("decorator start")
level=self.level
print level
func(*args,**kw)
print("decorator end")
return wrapper
@log(level='warnning')
def funcsss(a,b,c=10):
print a+b+c
funcsss(1,2,5)
decorator start
warnning
8
decorator end
小结:装饰器是对原函数、对象的重新封装,对函数功能的额外加强。装饰器除了本文提到的,还有内置装饰器等等