装饰器
下面用一个例子引出装饰器函数:
作为一个会写函数的python开发,我们从今天开始就要去公司上班了,写了一个函数就交给其他开发人员用了。
def func1(): print('hello world')
年末,老板要发奖金,就提议对这段日子所有人开发的成果进行审核,审核的标准是什么呢?就是统计每个函数的执行时间。
这个时候你要怎么做呀?
你一想,这好办,把函数一改:
import time def func1(): start=time.time() print('hello world') end=time.time() print(end-start) func1()
来公司半年,写了2000+函数,挨个改一遍,1个礼拜过去了,等领导审核完,再挨个给删了。。。又1个礼拜过去了。。。这是不是很闹心?
你觉得不行,不能让自己费劲儿,告诉所有开发,现在你们都在自己原本的代码上加上一句计算时间的语句?
import time def func1(): print('hello world') start=time.time() func1() end=time.time() print(end-start)
还是不行,因为这样对于开发同事来讲实在是太麻烦了。
那怎么办呢?你灵机一动,写了一个timer函数。。。
import time def timer(func): start=time.time() func() end=time.time() print(end-start) def func1(): print('hello world1') def func2(): print('hello world2') timer(func1) timer(func2)
这样看起来是不是简单多啦?不管我们写了多少个函数都可以调用这个计时函数来计算函数的执行时间了。。。尽管现在修改成本已经变得很小很小了,但是对于同事来说还是改变了这个函数的调用方式,假如某同事因为相信你,在他的代码里用你的方法用了2w多次,那他修改完代码你们友谊的小船也就彻底地翻了。
你要做的就是,让你的同事依然调用func1,但是能实现调用timer方法的效果。
import time def timer(func): start=time.time() func() end=time.time() print(end-start) def func1(): print('hello world1') func1=timer#很可惜这样做会报错 func1()
非常可惜,上面这段代码是会报错的,因为timer方法需要传递一个func参数,我们不能在赋值的时候传参,因为只要执行func1 = timer(func1),timer方法就直接执行了,下面的那句func1根本就没有意义。到这里,我们的思路好像陷入了僵局。。。
装饰器的形成过程:
import time def func1(): print('hello world') def timmer(func): def inner(): start=time.time() func() end=time.time() print(end-start) return inner func1=timmer(func1)#这句比较碍眼 func1()
忙活了这么半天,终于初具规模了!现在已经基本上完美了,唯一碍眼的那句话就是还要在做一次赋值调用。。。
你觉得碍眼,python的开发者也觉得碍眼,所以就为我们提供了一句语法糖来解决这个问题!
语法糖:
import time def timmer(func): def inner(): start=time.time() func() end=time.time() print(end-start) return inner @timmer #==> func1 = timmer(func1) def func1(): print('hello world') func1()
到这里,我们可以简单的总结以下装饰器:
装饰器的本质:就是一个闭包函数
装饰器的功能:再不修改原来函数的调用的情况下,对原函数的功能进行扩展
还有最后一个问题要解决,刚刚我们讨论的装饰器都是装饰不带参数的函数,现在要装饰一个带参数的函数怎么办呢?
import time
def timmer(func): def inner(a): start=time.time() func(a) end=time.time() print(end-start) return inner @timmer def func1(a): print('hello world',a) func1(1)
其实装饰带参的函数并不是什么难事,但假如你有两个函数,需要传递的参数不一样呢?
import time def timmer(func): def inner(*args,**kwargs): start=time.time() func(*args,**kwargs) end=time.time() print(end-start) return inner @timmer #==> func1 = timer(func1) def func1(a): print('hello world',a) @timmer #==> func2 = timer(func2) def func2(a,b): print(a,b) func1(1) func2('aaaa','bbb')
现在参数的问题已经完美的解决了,可是如果你的函数是有返回值的呢?
import time def timmer(func): def inner(*args,**kwargs): start=time.time() ret=func(*args,**kwargs)#接收返回值 end=time.time() print(end-start) return ret#return返回值 return inner @timmer #==> func1 = timer(func1) def func1(a): print('hello world',a) @timmer #==> func2 = timer(func2) def func2(a,b): print(a,b) return 'func2 over'#有返回值 func1(1) print(func2('aaaa','bbb'))
刚刚那个装饰器已经非常完美了,但是正常我们情况下查看函数的一些信息的方法在此处都会失效,我们先来了解两个方法__name__方法和__doc__方法
def index(): '''这是一个主页信息''' print('from index') print(index.__name__)#这个方法返回函数的名字 print(index.__doc__)#这个方法返回函数的注释
#输出结果为
index
这是一个主页信息
知道了这两个方法,我们再来看上面的例子
import time def timmer(func): def inner(*args,**kwargs): start=time.time() ret=func(*args,**kwargs)#接收返回值 end=time.time() print(end-start) return ret#return返回值 return inner @timmer #==> func2 = timer(func2) def func2(a,b):
print(a,b) return 'func2 over'#有返回值 func2('aaaa','bbb') print(func2.__name__)#输出为inner print(func2.__doc__)#输出为None
那有什么办法能让函数输出原来的函数名和注释呢
我们在装饰器上再加一点东西来完善它
import time from functools import wraps#加入这个模块 def timmer(func): @wraps(func)#加在最内层函数的上面,这其实是一个带参数的装饰器 def inner(*args,**kwargs): start=time.time() ret=func(*args,**kwargs)#接收返回值 end=time.time() print(end-start) return ret#return返回值 return inner @timmer #==> func2 = timer(func2) def func2(a,b): print(a,b) return 'func2 over'#有返回值 func2('aaaa','bbb') print(func2.__name__)#输出func2 print(func2.__doc__)#输出‘这是func2’
装饰器的开放封闭原则:
1、对扩展是开放的
为什么要对扩展开放呢?
我们说,任何一个程序,不可能再设计之初就已经想好了所有的功能并且未来不做任何更新和修改,所以我们必须允许代码扩展,添加新功能
2、对修改是封闭的
为什么要对修改封闭呢
就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给他人使用了,如果这个时候我们对其进行修改,很有可能影响到其他已经在使用该函数的用户
装饰器完美的遵循了开放封闭原则
装饰器的主要功能和装饰器的固定格式:
装饰器的主要功能:
在不改变函数调用方式的基础上在函数的前、后添加功能。
装饰器的固定格式:
def wrapper(func): def inner(*args,**kwargs): #在被装饰函数之前要做的事情 ret=func(*args,*kwargs) #在被装饰函数之后要做的事情 return ret return inner
#wraps版 from functools import wraps def wrapper(func): @wraps(func) #加在最内层函数的上面 def inner(*args,**kwargs): #在被装饰函数之前要做的事情 ret=func(*args,*kwargs) #在被装饰函数之后要做的事情 return ret return inner
带参数的装饰器:
假如你有成千上万个函数使用了一个装饰器,现在你想把这些装饰器都取消掉,你要怎么做?
一个一个的取消掉? 没日没夜忙活3天。。。
过两天你领导想通了,再让你加上。。。
def outer(flag): def wrapper(func): def inner(*args,**kwargs): if flag: print('执行函数之前要做的事') ret=func(*args,**kwargs) if flag: print('执行函数之后要做的事 ') return inner return wrapper @outer(True) def func(): print('hello world') func()
这样就可以通过修改flag的值来执行装饰器里不同的语句
多个装饰器装饰同一个函数:
def wrapper1(func): def inner1(*args,**kwargs): print('first inner1') ret=func(*args,**kwargs) print('second inner1') return ret return inner1 def wrapper2(func): def inner2(*args,**kwargs): print('first inner2') ret=func(*args,**kwargs) print('second inner2') return ret return inner2 @wrapper2 @wrapper1 def fun(): print('hello world') fun()
输出结果为:
first inner2
first inner1
hello world
second inner1
second inner2
具体过程见下图,按照序号一步步执行