前言
在学python装饰器的时候,网上的文章一般就是前面看得懂然后突然就看不懂了,也可能是脑子不好。所以写下来方便自己忘记以后查阅,也帮助一下后来者更好的理解python装饰器。
正文
python装饰器的作用
简单来说就是在不改动原有函数的基础上为这个函数添加新的功能。应用场景:插入日志,登录授权等。我没用过我也不知道,但是以后去企业工作了肯定是用得上的。例如老板让你添加新的功能,你总不能改动函数里面的代码吧,毕竟企业级代码都是祖传代码,不知道多少人写过的,你一不小心动了什么不该动的东西很有可能让程序崩溃,所以这时候装饰器的作用就显得尤为重要了。
python装饰器的使用
python万物皆对象,既然都是对象,那函数传的参数可以是字符串对象,整数对象,当然也可以是函数对象。
def wrapper(func):
def inner():
print("假设在函数执行需要打印我")
re = func()
print("假设在函数完成后需要打印我")
return re
return inner
def func():
print("in func")
return "func return"
func = wrapper(func)
print(func)
print(func())
"""
运行结果
<function wrapper.<locals>.inner at 0x0000023344A3F510>
假设在函数执行需要打印我
in func
假设在函数完成后需要打印我
func return
"""
这段代码估计大家都能看得懂,将func函数当作参数传给wrapper函数,在func函数执行前后对他进行一些操作。这就是python装饰器最简单的用法。
python语法糖@的使用
语法糖没有增加新功能,只是换了一种写法,没有它不影响你程序的实现。
在被装饰函数前写@wrapper相当于func=wrapper(func),以上代码也等价于下面的代码
def wrapper(func):
def inner():
print("假设在函数执行需要打印我")
re = func()
print("假设在函数完成后需要打印我")
return re
return inner
@wrapper
def func():
print("in func")
return "func return"
print(func)
print(func())
不过使用@语法糖有一个不同的地方就是无论你有没有调用func函数他都会执行装饰函数(也就是wrapper函数),它是在加载模块的时候就执行了@wrapper这个操作(可以理解为func=wrapper(func))。所以你如果在装饰器函数中不止是返回一个函数对象,而是还做了某些操作(比如打印、程序休眠等),并且你还不调用被装饰函数,可能就会导致程序的运行出现bug(不过一般人不会这么做,我也是无意中发现的)。
装饰器传参数
如果你理解了上面所讲的那么传参数也能轻易的理解,看个例子
def wrapper(func):
def inner(a,b,*args,**kwargs):
print("假设在函数执行需要打印我")
re = func(a,b,*args,**kwargs)
print("假设在函数完成后需要打印我")
return re
return inner
@wrapper
def func(a,b,*args,**kwargs):
print("in func")
print(a,b)
print(args,kwargs)
return "func return"
print(func)
print(func(1,2,3,4,5,k=5,j=6))
"""
运行结果
<function wrapper.<locals>.inner at 0x0000020A61FDF510>
假设在函数执行需要打印我
in func
1 2
(3, 4, 5) {'k': 5, 'j': 6}
假设在函数完成后需要打印我
func return
"""
很容易理解嘛,运行func就相当于是运行wrapper函数里面的inner,前两个参数给是给a,b的,后面的动态传参,没键值的以元组形式给args,有键值的以字典形式给kwargs。
看到这里你可能觉得你懂了,不要急我们在看一下下面这个例子
多层嵌套的装饰器
import time
def timmer(*args,**kwargs):
print(args,kwargs)
def wrapper(f):
print(args,kwargs)
def inner(*args,**kwargs):
print(args,kwargs)
if flag:
start_time = time.time()
ret = f(*args,**kwargs)
time.sleep(0.3)
end_time = time.time()
print('此函数的执行效率%f' % (end_time-start_time))
else:
ret = f(*args, **kwargs)
return ret
return inner
return wrapper
flag = True
@timmer(flag,2,3)
def func1(*args,**kwargs):
return 666
#func1 = timmer(flag,2,3)(func1)
print(func1(1,2))
"""
运行结果
(True, 2, 3) {}
(True, 2, 3) {}
(1, 2) {}
此函数的执行效率0.300833
666
"""
如果没注意按照之前的思路走,func1 = wrapper,func1(1,2) = wrapper(1,2),但是这程序的运行结果跟我们想的完全不一样,从运行结果很容易知道程序已经调用了inner()函数了,但是wrapper函数只是返回inner,要调用inner函数应该是func1(1,2)()这样才对。然后与之前代码不同的地方找一下问题出在哪,细心的小伙伴应该能发现,之前使用语法糖@后面只是接对象地址,这回接的像是函数的调用。没错,为题就在这。
正确思路
其实我们就把@timmer(flag,2,3)理解为:先调用timmer(flag,2,3)函数返回一个对象wrapper,然后再执行@wrapper。根据之前所学,可以使用func1 = timmer(flag,2,3)(func1)替代@timmer(flag,2,3),运行结果一样。需要注意的是flag是一个全局变量,虽然我们没有传参数,但还是可以使用。
(有一个地方没有搞清楚,在wrapper里面打印args和kwargs能打印出传给timmer的值,如果有小伙伴知道望告知,或者我搞清楚了再更新)
然后还有一些多个装饰器装饰一个函数的、wraps模块使用的,这些没什么难点很好理解,网上有很多我就不在赘述了,可以看这里,文中部分代码来自这篇文章,这也是我目前看到的讲得最好的一篇。
总结
简单来说在func1前加@wrapper就等于在程序开始加载模块的时候运行了func1=wrapper(func1),而@wrapper(1,2)就等于a=wrapper(1,2) func1=a(func1)