python学习——装饰器函数
一、装饰器函数的作用是什么
答:装饰器函数是在不修改原函数及其调用方式的情况下对原函数功能进行扩展
对于搞python开发的人来说,函数占据了至关重要的地位。都说学好函数你就可以去找工作了,好了,假如你已经在某个公司上班了,想到马上就要过年了,那过年就意味着你可以向老板要年终奖金了,那凭什么老板要给你年终奖呢,你又能得到多少年终奖金呢。老板是这样说的,你给我至少写1000行代码,我来看一下你代码的执行时间,根据你的执行时间来评你的年终奖。好了,你知道既然要计算时间,学过函数的你就来实现一个计算函数执行时间的函数呗。聪明的你是这样的。
1 import time 2 3 def qqxing(): 4 start = time.time() 5 print('老板好,同事好,大家新年好') 6 end = time.time() 7 elapsed_time = (end - start) 8 print(elapsed_time) 9 10 qqxing() 11 12 #运行结果:老板好,同事好,大家新年好 13 # 0.0
实现了计算时间的函数,可是你发现了执行时间为0,因为你写得函数太厉害了,但如果是0的话怕吓到老板,所以你这样搞。
1 import time 2 3 def qqxing(): 4 start = time.time() 5 time.sleep(0.1) 6 print('老板好,同事好,大家新年好') 7 end = time.time() 8 elapsed_time = (end - start) 9 print(elapsed_time) 10 11 qqxing() 12 13 #运行结果:老板好,同事好,大家新年好 14 # 0.10044193267822266
ok,虽然实现了,但是你写了500个函数,你难道要每个函数都加上计算时间的函数吗?能不能这样搞?
1 import time 2 3 def timmer(func): 4 start = time.time() 5 func() 6 end = time.time() 7 print(end - start) 8 9 def func1(): 10 print('func1 over') 11 12 def func2(): 13 print('func2 over') 14 15 timmer(func1) 16 timmer(func2)
这样是不是好多了,基本可以实现,但你会发现实际上当我调用func()函数时,调用方式改变了,由原来的func()变成timmer(func)了,那能不能不改变原函数的调用方式呢?可不可以这样。
1 import time 2 3 def timmer(func): 4 start = time.time() 5 func() 6 end = time.time() 7 print(end - start) 8 9 def func(): 10 print('func over') 11 func = timmer 12 timmer()
这样就完美了,可惜我太天真,当执行函数时会报错,因为timmer(func)要传一个值,而你明目张胆的就给python解释器来一个func = timmer,这哪行!而我们在用函数作为变量赋值的时候是没法传参数的。所以你没法了。至此你得出了一个结论:年终奖与我无缘!😄😄😄 但是你是一个不服输的人,冥思苦想了1个小时。得到了如下一个强大的功能。
二、装饰器的形成过程
1 import time 2 3 def timmer(func): 4 def inner(): 5 start = time.time() 6 func() 7 end = time.time() 8 print(end - start) 9 return inner 10 def func(): 11 time.sleep(0.1) 12 print('func over') 13 func = timmer(func) 14 func() 15 16 #运行结果: func over 17 # 0.10010719299316406
确实没有改变func()的调用方式。这儿其实你应该知道你用了一个闭包函数,在这儿介绍一下闭包函数。
什么是闭包函数:即在内部函数中调用外部函数的变量,********注意:外部变量不包含全局变量**********
但是有个地方很碍眼,就是这个func = timmer(func),你怕老板少给你年终奖,於是你到万能的朋友圈,博客,百度一顿搜索,得到了一颗糖,叫“语法糖” 👇
1 import time 2 3 def timmer(func): 4 def inner(): 5 start = time.time() 6 func() 7 end = time.time() 8 print(end - start) 9 return inner 10 @timmer 11 def func(): 12 time.sleep(0.1) 13 print('func over') 14 #func = timmer(func) 15 func()
这是一颗很甜的糖,你可开兴了。於是你把语法糖的用法写到了下面👇
语法糖的用法就是,@函数名 其中函数名是装饰器函数的函数名,而且@函数名必须在被装饰的函数上面,虽然他们没有在一起,但他们就像亲人一样。😄😄😄
然后你又小结了一下装饰器👇
装饰器的本质:一个闭包函数
装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展。
简单的装饰器如下:
1 import time 2 3 def timmer(func) 4 def inner() 5 start = time.time() 6 func() 7 print(time.time() - start) 8 retuan inner 9 10 @timmer 11 def func() 12 print('hello wrapper') 13 14 func()
其中,定义的timmer(func)是装饰器函数,func()是被装饰的函数,它必须紧紧地挨着语法糖,语法糖就相当于“@timmer ——> func = timmer(func) ”在timmer(func)里面有一个inner(),在inner()里面的func()前面和后面我们可以加一些必要的功能。这才是装饰器真正的厉害之处。
三、装饰器的进阶
上面我们得到了一个很low的装饰器,但还没完,机智的你会发现你调用函数的时候没有传值,下面我们来说一下传值的问题,学习函数我们知道对形参传值的时候如果你不确定你到底要传多少值的时候我们可以这样传。
1 def func(*args,**kwargs): 2 3 print('这是一个万能的传值方式') 4 5 func()
所以我们的装饰器有变了个样👇
1 import time 2 3 def timmer(func): 4 def inner(*args,**kwargs): 5 start = time.time() 6 ret = func(*args,**kwargs) 7 end = time.time() 8 print(end - start) 9 return ret 10 return inner 11 @timmer 12 def func1(a,b): 13 time.sleep(0.1) 14 print('func1 over and get {} and {}'.format(a,b)) 15 func1(1,['a','b','c'])
此时的装饰器已经堪称完美,但是由于原函数被装饰的原因我们不能查看原函数的“注释”等内容,如下:
1 import time 2 3 def timmer(func): 4 def inner(*args,**kwargs): 5 start = time.time() 6 ret = func(*args,**kwargs) 7 end = time.time() 8 print(end - start) 9 return ret 10 return inner 11 @timmer 12 def func1(a,b): 13 """返回a,b的值""" 14 time.sleep(0.1) 15 print('func over and get {} and {}'.format(a,b)) 16 print(func1.__doc__) 17 print(func1.__name__) 18 func1(1,['a','b','c']) 19 20 #运行结果: func over and get 1 and ['a', 'b', 'c'] 21 # None 22 # inner 23 # 0.10055851936340332
所以你有对装饰器函数进行了改装。👇
1 from functools import wraps 2 import time 3 4 def timmer(func): 5 @wraps(func) 6 def inner(*args,**kwargs): 7 start = time.time() 8 ret = func(*args,**kwargs) 9 end = time.time() 10 print(end - start) 11 return ret 12 return inner 13 @timmer 14 def func1(a,b): 15 """返回a,b的值""" 16 time.sleep(0.1) 17 print('func over and get {} and {}'.format(a,b)) 18 print(func1.__doc__) 19 print(func1.__name__) 20 func1(1,['a','b','c']) 21 22 #运行结果:func over and get 1 and ['a', 'b', 'c'] 23 # 返回a,b的值 24 # func1 25 # 0.10077428817749023
这样装饰器基本搞定了。下面再来对装饰器作一些说明。
四、装饰器的再次总结
1.装饰器是一个在不改变原函数的调用方式的情况下对函数进行拓展;
2.装饰器的本质是一个闭包函数;
3.装饰器完美的诠释了编程开发中六大原则之一的“开放封闭原则”
4.开放封闭原则:
1.1 对扩展是开放的
为什么要对扩展开放呢?
我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。
2.1 对修改是封闭的
为什么要对修改封闭呢?
就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。
5. 装饰器的固定模式
1 from functools import wraps 2 3 def timmer(func): 4 @wraps(func) 5 def inner(*args,**kwargs): 6 """在被装饰函数之前添加的功能""" 7 ret = func(*args,**kwargs) 8 """在被装饰函数之后添加的功能""" 9 return ret 10 return inner 11 @timmer 12 def func1(a,b): 13 """返回a,b的值""" 14 print('func over and get {} and {}'.format(a,b)) 15 16 func1(1,['a','b','c'])
五、装饰器的拓展
1.带参数的装饰器
为了年终奖你费气八力的搞个装饰器蒙混了老板,得到了20个月的年终奖金,那么问题又来了!
假如你有成千上万个函数使用了一个装饰器,现在你想把这些装饰器都取消掉,你要怎么做?一个一个的取消掉? 没日没夜忙活3天。。。过两天你领导想通了,再让你加上。。。
崩溃中又带着点庆幸,万一又有奖金呢。继续一顿捣鼓。。。
1 from functools import wraps 2 3 def hahaha(flag): 4 def timmer(func): 5 @wraps(func) 6 def inner(*args,**kwargs): 7 if flag: 8 """在被装饰函数之前添加的功能""" 9 print('**********') 10 ret = func(*args,**kwargs) 11 if flag: 12 """在被装饰函数之后添加的功能""" 13 print('##########') 14 return ret 15 return inner 16 return timmer 17 @hahaha(True) 18 def func1(): 19 print('哈哈哈') 20 21 func1() 22 23 #运行结果:********** 24 # 哈哈哈 25 # ########## 26 27 from functools import wraps 28 29 def hahaha(flag): 30 def timmer(func): 31 @wraps(func) 32 def inner(*args,**kwargs): 33 if flag: 34 """在被装饰函数之前添加的功能""" 35 print('**********') 36 ret = func(*args,**kwargs) 37 if flag: 38 """在被装饰函数之后添加的功能""" 39 print('##########') 40 return ret 41 return inner 42 return timmer 43 @hahaha(False) 44 def func1(): 45 print('哈哈哈') 46 47 func1() 48 49 # 运行结果:哈哈哈
不知不觉你又完成了一个强大的功能。只要在语法糖的地方稍稍改一下flag的布尔值就行了。哈哈哈。。。
*******************注意:装饰器最多只能定义三层嵌套函数,这已经是它的极限了************************
2. 多个装饰器装饰一个函数
在实际的开发项目中,我们还会遇见多个装饰器函数装饰一个函数的情况,请看下面👇
1 from functools import wraps 2 3 def wrapper1(func): 4 def qqxing(*args,**kwargs): 5 print('qqxing') 6 ret1 = func(*args,**kwargs) 7 print('shuangwaiwai') 8 return ret1 9 return qqxing 10 11 def wrapper2(func): 12 def wahaha(*args,**kwargs): 13 print('wahaha') 14 ret2 = func(*args,**kwargs) 15 print('**************') 16 return ret2 17 return wahaha 18 @wrapper2 # func = wrapper2(func) 19 @wrapper1 # func = wrapper1(func) 20 def func(): 21 print('I am a veryvery handsome boy') 22 23 func() 24 25 # 运行结果: wahaha 26 # qqxing 27 # I am a veryvery handsome boy 28 # shuangwaiwai 29 # **************
如果你仔细看的话,你会发现在装饰器函数中你添加的功能在打印的时候其顺序是关于被装饰的函数的输出内容对称的。是不是和俄罗斯套娃非常相像(虽然我都不知道俄罗斯套娃),如果你把打印的内容改一下就可以更加清楚的观察了,但是我就不改,要改你自己改去。反正写到这儿我感觉好累。不过注意一点,这个程序的执行过程还是有点复杂的,请看下面这张图,我不确定能不能正确的画出来,因为我累╯︿╰
好了,执行过程大概就这样,所以你要明白,想要别人方便你就得付出更多,但是如果能够作为一个python开发工作人员我乐意,我自豪,我有成就感。
这是今天的装饰器函数,若还有什么地方忘记写的以后会补上。
以上内容参考了Eval_J老师的python之路——装饰器函数,有兴趣的朋友们可以去看一下。链接 👉 https://www.cnblogs.com/Eva-J/articles/7194277.html#3999110