一、装饰器定义:
1.装饰器的本质为函数;
2.装饰器是用来完成被修饰函数的附加功能的
所以:装饰器是用来完成被修饰函数附属功能的函数
装饰器的要求:
1.不能修改被修饰函数的源代码;
2.不能更改被修饰函数的运行方式;
3.上述两者缺一不可。
二、装饰器的构成:
装饰器=高阶函数+函数嵌套+闭包
装饰器的简单框架:
import time
#装饰器的简单框架
def run_time(fun):#传入参数为函数
def wrapper():#函数嵌套
fun() #闭包(函数作用域)
return wrapper #返回wrapper函数名
#要求完成一个装饰器,用来计算函数的运行时间
#高阶函数
def run_time(fun):
def wrapper():
startime = time.time()
fun()
endtime = time.time()
print("函数运行时间为%s"%(endtime-startime))
return wrapper
#被装饰的函数
def test():
time.sleep(2)
#完成装饰功能
test = run_time(test) #运行run_time函数,返回test函数的内存地址
test() #运行test函数
截止目前为止,既未改变test函数的源代码又未改变test函数的运行方式,还为test函数加上了计算它运行时间的功能,符合装饰器的要求。
三、优化使用br/>Python中直接使用@符号来调用装饰器,例如:
#被装饰的函数
def test():
time.sleep(2)
#完成装饰功能
test = run_time(test) #运行run_time函数,返回test函数的内存地址
test() #运行test函数
@run_time
def test():
time.sleep(2)
test()
两者的功能完全相同,即@run_time与test = run_time(test)的代码块功能完全相同
四、加入返回值
若想在test函数中加上返回值,怎么办?
首先按照最常规的办法来处理,代码如下:
@run_time #相当运行wrapper
def test1():
time.sleep(2)
return "test1函数运行完毕"
test1()
可以发现,并没有返回我们想要的结果(test1函数运行完毕),为什么呢?从上面内容可知,我们在运行test1函数时,实际上是在调用run_time函数,最终也是在调用warpper函数,此时,返回的结果wrapper函数中的结果。但wrapper函数中并没有返回值,所以上面的装饰器没有得到我们想要的结果。现进行改进,代码块如下:
#装饰器的简单框架
def run_time(fun):
def wrapper():
startime = time.time()
res = fun()
endtime = time.time()
print("函数运行时间为%s"%(endtime-startime))
return res
return wrapper
@run_time #相当运行wrapper
def test2():
time.sleep(2)
return "test1函数运行完毕"
res = test2()
print(res)
运行结果如下图:
五、带参数函数的装饰器
我们知道,大部分的函数都是有参数的,没有参数的函数一般意义不大。
那么,如何用装饰器来修饰函数呢?
首先,我们已经明白了上面的例子。在运行函数test时,实际上就是装饰器函数中的嵌套函数warpper,同时这个装饰器的功能可以附加给任意函数,也就是说test函数时可变的,即test函数的参数不定,如何能够让warpper能够接受任意被修饰函数(test)的参数呢?我们知道,为了解决此问题,python在函数中应用了类似元组和字典的方式来存放任意数量函数的位置参数和关键字参数。这样我们就可以很好解决修饰器中的参数问题了,见下面代码块:
#带参数的函数装饰器
#装饰器的简单框架
def run_time(fun):
def wrapper(*args,*kwargs):
startime = time.time()
res = fun(args,**kwargs)
endtime = time.time()
print("函数运行时间为%s"%(endtime-startime))
return res
return wrapper
@run_time #相当运行wrapper
def test2(name,age):
time.sleep(2)
print("姓名为:%s,年龄为:%s"%(name,age))
return "test1函数运行完毕"
@run_time #相当运行wrapper
def test3(name,age,sex):
time.sleep(2)
print("姓名为:%s,年龄为:%s,性别为:%s"%(name,age,sex))
return "test1函数运行完毕"
test2('zhang',24)
test3('zhang',24,sex='man')
运行结果如下图:
六、带参数的装饰器
装饰器也是函数,所以当有需要的时候,装饰器也是可以带参数的。那么如何使用带参数的装饰器呢?我们从上面的例子中知道,装饰器函数(run_time)的参数是func(即为所要装饰的函数),那么此处肯定不能带参数了。如何解决这个问题呢?还记得前面讲到的装饰器是由高阶函数+函数嵌套+闭包组成的吗?此处就要用到闭包的概念了,闭包可以理解成变量的作用域。我们知道,在使用函数嵌套时,变量是使用LEGB的原则(https://blog.51cto.com/10836356/2094956)。这样我们就可以使用以下代码来解决该问题了,代码块如下:
#带参数的装饰器
#装饰器的简单框架
def type_sex(sex):
if sex =='man':
def run_time(fun):
def wrapper(*args,kwargs):
startime = time.time()
res = fun(*args,*kwargs)
endtime = time.time()
print("函数运行时间为%s"%(endtime-startime))
return res
return wrapper
return run_time
else:
def run_time(fun):
def wrapper(args,kwargs):
res = fun(*args,**kwargs)
endtime = time.time()
print("该函数运行完的时间为%s"%(endtime))
return res
return wrapper
return run_time
@type_sex(sex='man') #相当运行wrapper
def test5(name,age):
time.sleep(2)
print("姓名为:%s,年龄为:%s"%(name,age))
return "test1函数运行完毕"
@type_sex(sex='woman') #相当运行wrapper
def test6(name,age,sex):
time.sleep(2)
print("姓名为:%s,年龄为:%s,性别为:%s"%(name,age,sex))
return "test1函数运行完毕"
test5("zhang",24)
test6("zhou",26,'woman')
运行结果如下图:
七、装饰器函数的运行过程