在了解函数的装饰器之前,我们先了解一个开发上的原则:开放封闭原则。
软件上线后,就要遵行这个原则,它就是对修改源代码是封闭的,对功能是开放的。为了遵行这个原则,我们就要找到一种好的解决方案来实现不修改一个功能源代码,以及它的调用方式的前提下,为它加上新的功能。
装饰器就是在不修改被装饰对象源代码与调用方式的前提下,为被装饰对象添加新功能。
我们来看一小段的代码:
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
面试引申:
def set_log(func):
print("__开始装饰set_log__")
def call_func():
print("__set_log__")
func()
return call_func
# 定义装饰器2
def set_func(func):
print("__开始装饰set_func__")
def call_func():
print("__set_func__")
func()
return call_func
# 定义函数,并添加装饰器
@set_log # 等价于 func1=set-log(func1)
@set_func # 等价于 func1=set_func(func1)
def func1():
print("__func__")
func1()