在了解函数的装饰器之前,我们先了解一个开发上的原则:开放封闭原则。
软件上线后,就要遵行这个原则,它就是对修改源代码是封闭的,对功能是开放的。为了遵行这个原则,我们就要找到一种好的解决方案来实现不修改一个功能源代码,以及它的调用方式的前提下,为它加上新的功能。
装饰器就是在不修改被装饰对象源代码与调用方式的前提下,为被装饰对象添加新功能。
我们来看一小段的代码:
import time def index(): time.sleep(3) print('这是一小段代码') index()
现在我们遇到了一个问题。我们要在不修改源代码和调用方式的情况下,添加计算出一下index()这个函数的运行时间。
我们可能马上反应到这样来修改:
import time def index(): start_time=time.time() time.sleep(1) print('这是一小段代码') stop_time=time.time() print('运行时间是:%s秒' %(stop_time-start_time)) index()
明眼人一看就知道,你这个代码,虽然调用方式没有变,功能也增加了,但是修改入了源代码了呀!所以不行。
后来你又想不如这样修改一下吧:
import time def index(): time.sleep(3) print('这是一小段代码')
start_time = time.time() index() stop_time = time.time() print('运行时间是:%s秒' % (stop_time - start_time))
上面的代码一看,index函数的源代码没有修改,调用方式也不变,也增加了功能,没问题。应该可以了吧。但技术大佬一看,你这个代码写的也太low了吧。如果又要计算其它的功能的运行时间,要不是不断的要复制下面的三句代码:
start_time = time.time() #index()其它的功能 stop_time = time.time() print('运行时间是:%s秒' % (stop_time - start_time))
这样不就造成代码冗余了吗。老板一看就知道你的技术太low了,想把你开了。
于是你就想,那就高端一点,我就把它们弄成函数来调用:
import time def index(): time.sleep(3) print('这是一小段代码') def wrapper(func): start_time= time.time() func() stop_time=time.time() print('运行时间是:%s秒' % (stop_time - start_time)) wrapper(index)看这代码是高端了一点,但是,你把函数的调用方式给改了。完全违背了开放封闭原则。还是要被老板开除了,于是你就请教技术大佬,大佬说这简单呀,你用闭包函数不就得了麻。这时你就懵逼了,什么是闭包函数呀,你不懂。
大佬就说,闭包函数就是定义在函数内部的函数,并且该函数包含对外部函数作用域中的名字的引用。你听了还是一脸的懵,大佬于是快速的敲了如下代码让你理解:
def outter(): name='monicx' def inner(): print('my name is %s' %name) return inner inner=outter() inner()
从这上面代码当中你看出了一些门道,outter()函数当中嵌套了一个inner()函数,inner()函数就是在outter()函数里面,一般内部函数在外部是不能被调用的,但是你看到了outter()函数体内的最后一句代码把inner()的名称空间返回了。所以当调用outter()的时候,就得到了inner函数的名称空间,于是把它当作一个值,赋值给一个变量名。于是把它赋给inner这个变量名。通过这个变量名加个()就可以调用里面的inner()函数。并且outter()函数作用域内的name变量的值还可以被inner()使用。于是你有点理解闭包函数的强大了。
于你迫不及待的用刚学到的知识点来优化之前写的low逼代码,得到了下面代码:
import time def index(): time.sleep(3) print('这是一小段代码') def auth(func):#func=最原始的index def wrapper(): start_time= time.time() func() stop_time=time.time() print('运行时间是:%s秒' % (stop_time - start_time)) return wrapper index=auth(index)#新的index=wrapper index()#wrapper()
上面的在auth(index)开始执行的时候,就开始执行auth()函数体内的代码,
并把源化码的index函数名称空间当作参数传入。赋值给func
然后在auth()函数体内定义了wrapper()函数,
接着把wrapper()函数的名称空间作为调用auth()函数的返回值。
把这个返回值赋给index变量,此时这个新的index=wrapper
因为wapper是函数名,所以index也是一个函数名。
所以接下通过新的index()就能调用auth()函数内的wrapper()函数的运行。
接着执行到func(),func()就是执行源代码的index()函数。
这么一系列下来,你发现此时的调的index()已经被装饰上了一个计算出一下index()这个函数的运行时间的功能,且没有改变原来index()函数体内的代码!!!感叹这样的装饰实在太棒了。
于是你就迫及待的想用它装饰别的函数。
import time def index(): time.sleep(3) print('这是一小段代码') def home(name): time.sleep(2) print('这是另一个%s的代码' %name) #==============装饰器=================== def auth(func): # func=最原始的index def wrapper(): start_time = time.time() func() stop_time = time.time() print('运行时间是:%s秒' % (stop_time - start_time)) return wrapper index = auth(index) # 新的index=wrapper home= auth(home) #===============end===================== index() # wrapper() home(name='moncix')
运行结果:
Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/untitled/day20/test.py", line 58, in <module> home(name='moncix') TypeError: wrapper() got an unexpected keyword argument 'name' 这是一小段代码 运行时间是:3.000171422958374秒
怎么会出bug呢!!!!哦原来传参出现了错误。index()是无参的函数,而home(name)是有参的,所以你就想到运用可变参数的知识(*:来解决按位置定义的实参,**:来解决按关键字定义的实参)就可以让这个装饰器,不管有参或无参函数都可以装饰了。改进后的代码如下:
import time def index(): time.sleep(3) print('这是一小段代码') def home(name): time.sleep(2) print('这是另一个%s的代码' %name) #==============装饰器=================== def auth(func): # func=最原始的index def wrapper(*args,**kwargs): start_time = time.time() func(*args,**kwargs) stop_time = time.time() print('运行时间是:%s秒' % (stop_time - start_time)) return wrapper index = auth(index) # 新的index=wrapper home= auth(home) #===============end===================== index() # wrapper() home(name='moncix')
这样代码就运行成功了!!!!
这是一小段代码 运行时间是:3.000171661376953秒 这是另一个moncix的代码 运行时间是:2.0001144409179688秒
后来你发现装饰器还有专门的语法糖:
先把装饰函数放在被装饰函数的前面,然后在被装饰函数的前面加上用一行:@装饰函数名,即可。
import time #==============装饰器=================== def auth(func): # func=最原始的index def wrapper(*args,**kwargs): start_time = time.time() func(*args,**kwargs) stop_time = time.time() print('运行时间是:%s秒' % (stop_time - start_time)) return wrapper #===============end===================== @auth #@auth等于index = auth(index) def index(): time.sleep(3) print('这是一小段代码') @auth#@auth等于home= auth(home(name)) def home(name): time.sleep(2) print('这是另一个%s的代码' %name) index() home(name='moncix')
运行结果不变,你发现装饰器的语法糖真的太好用了,在用它的时候只要在被装饰函数上面用专门的一行来@它就好了。
后来你写了不少的装饰器,你总结出了一个短小精悍的装饰器的模板供那些小白参考:
不自带参装饰器:
def outter(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
自带参装饰器:
def parameters(x, y, z): def outter(func): def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res return wrapper return outter