目录
一、装饰器
1、装饰器作用
在函数名以及函数体不改变的前提下,给一个函数附加一些额外的代码。
目的:
- 使得代码符合‘开放封闭原则’,‘开放’:如果想要新增功能,在原先代码的基础上,单独进行扩展;‘封闭’:已经写好的代码,尽可能不要修改
- 使得代码符合‘单一性原则’:一个函数仅仅完成一个功能。
2、装饰器实现
假设当前要实现一个发消息的功能,但是要求在发信息之前需要进行验证。可以很容易写出以下程序:
#功能函数
def sendMessage():
print('发消息')
#登录验证函数
def checkLogin():
print('验证通过')
即:将发消息功能和登录验证功能分别用不同的函数进行实现,使用时将checkLogin()函数放入sendMessage()函数中实现即可。
但是可以发现,若如此则发消息功能的函数中同时也具有了验证功能,若想不将验证函数代码写入发消息函数但是依然想让发消息函数具有验证功能,可以通过以下方式实现。
(1)法一:无语法糖
#功能函数
def sendMessage():
print('发消息')
#登录验证装饰器
def checkLogin_decorator(func):
def inner():
print('验证通过')
func()
return inner
sendMessage = checkLogin_decorator(sendMessage)
sendMessage()
以上代码中,sendMessage()函数从代码上来说并未发生改变,但是最后调用的sendMessage()函数具有了验证功能,结果如下所示:
将上述代码使用语法糖方式实现,则如下:
(2)法二:语法糖
#登录验证装饰器
def checkLogin_decorator(func):
def inner():
print('验证通过')
func()
return inner
@checkLogin_decorator
def sendMessage():
print('发消息')
sendMessage()
仔细观察上述代码可知 ,法二与法一的区别仅仅在于:法二使用@checkLogin_decorator语句替代了法一的:sendMessage = checkLogin_decorator(sendMessage)语句。
此时的函数checkLogin_decorator()让sendMessage()函数拥有了新的功能,相当于‘装饰’了sendMessage()函数,故函数checkLogin_decorator()被称为------装饰器。
- 装饰器视频讲解资源参照:https://www.bilibili.com/video/BV12b411g7G7?p=152
- 装饰器执行过程:https://www.bilibili.com/video/BV12b411g7G7?p=154
二、装饰器执行时间
装饰器立即执!!!
以以上代码为例,当写下@checkLogin_decorator时该装饰器就已经执行了。如下:
# 登录验证装饰器
def checkLogin_decorator(func):
print('装饰器执行了')
def inner():
print('验证通过')
func()
return inner
@checkLogin_decorator
def sendMessage():
print('发消息')
以上代码中,并未调用sendMessage()函数,但是运行后可发现checkLogin_decorator()函数中的print(‘装饰器执行了’)已被执行;函数inner()中的内容在调用sendMessage()函数后才被执行。
三、装饰器进阶
1、装饰器叠加
(1)叠加原则
- 从上到下装饰:装饰时位于上面的装饰器对下面的装饰器及函数进行装饰
- 从下到上执行:执行时下面的装饰器先执行,上面的装饰器后执行
(2)代码实现
如下所示,在打印‘我输出了!!!’这段文字前要求先打印*及-,采用装饰器实现如下:
def print_line(func):
def inner():
print('------------------------------')
func()
return inner
def print_star(func):
def inner():
print('************************************')
func()
return inner
@print_star
@print_line
def output():
print('我输出了!!!')
output()
代码详解:
- 由上到下装饰:上述代码中,由上到下装饰,即print_star装饰print_line及output()函数构成的整体,而print_line装饰output()函数
- 从下到上执行:
代码中语句@print_line可以将其等价内容写出来,即
@print_line等价于 :output = print_line(output) 写出其函数体,也就是装饰后的新函数output为:
print('------------------------------')
print('我输出了!!!')
代码中语句@print_star可以将其等价内容写出来,即
print_star等价于 :output = print_star(output) 写出其函数体为,也就是经过将经过print_line装饰后再经过print_star装饰的新函数output为:
print('************************************')
print('------------------------------')
print('我输出了!!!')
由故代码输出为:
2、对有参数的函数进行装饰
总原则:
1. 无论什么场景,保证函数调用参数个数一致
2. 为了通用,可以使用不定长参数,结合拆包操作进行处理
(1)被装饰函数有一个参数
在上面的代码中,若被装饰函数中带有一个参数,可进行如下装饰:
def print_line(func):
def inner(n):
print('------------------------------')
func(n)
return inner
@print_line
def output(num):
print('我输出了:%d'%num)
output(100)
如上程序中,通过分析可知,程序的目的在于:在打印‘我输出了:100’这句话之前打印一串横线。函数output()中带有一个参数num,而@print_line就等价于:output = print_line(output),而最下方的调用函数output()就相当于inner(),故必须给inner()传入参数,最下方output()函数调用时传入100,故装饰器中用参数n来接收100;同时,inner()函数中的func()函数用和inner()函数相同的参数n来接收100就可以实现需求打印的需求。
(2)被装饰函数有一个或多个参数(通用方法)
有时被装饰函数中可能带有多个参数或者带有关键字参数,此时使用该方法即可:
def print_line(func):
def inner(*args,**kwargs): #使用不定长参数,无需管被装饰函数有几个参数
print('------------------------------')
func(*args,**kwargs) #使用不定长参数解包
return inner
@print_line
def output(num1,num2):
print(num1,num2)
output(123,456)
结果:
带有关键字参数的情形:
def print_line(func):
def inner(*args,**kwargs): #使用不定长参数,无需管被装饰函数有几个参数
print('------------------------------')
func(*args,**kwargs) #使用不定长参数解包
return inner
@print_line
def output(num1,num2,num3):
print(num1,num2,num3)
output(123,456,num3=20)
结果:
视频讲解链接:
https://www.bilibili.com/video/BV12b411g7G7?p=155
3、对有返回值的函数进行装饰
**原则:**无论什么场景,保证函数的返回值一致
如下所示,被装饰函数的返回值是:7
def output(num1,num2,num3):
return num1 + num2 + num3
而采用装饰器则可以使用以下方法:
def print_line(func):
def inner(*args,**kwargs):
print('------------------------------')
result = func(*args,**kwargs)
return result
return inner
@print_line
def output(num1,num2,num3):
print(num1,num2,num3)
return num1 + num2 + num3
rest = output(1, 4, num3=2)
print(rest)
上述代码中,output()函数带有返回值,有num1,num2,num3三个参数。按照原则:无论什么场景,保证函数的返回值一致。即output()函数和inner()函数的参数和返回值均应该保持一致。inner()函数中*args,**kwargs与output()函数中的参数对应,inner()的返回值应该与output()函数对应,output()函数的返回值也就是func(*args,**kwargs)的返回值,故inner()的返回值应该是unc(*args,**kwargs)的返回值。
上述代码的运行结果如下,可以看出,返回值为rest的值,也就是7。
视频讲解链接:https://www.bilibili.com/video/BV12b411g7G7?p=156
4、带有参数的装饰器
def decorator(func):
def inner():
print('*'*20)
func()
return inner
@decorator
def output():
print('hello world')
output()
上述代码可以实现一个打印hello world之前打印*的功能,但是若想要将装饰器中的打印星星的功能不写死,要求装饰器可以接收不同的符号,根据接收符号的不同可以打印不同的内容,可以用下述方式实现:
def get_decorator(char):
def decorator(func):
def inner():
print(char * 20)
func()
return inner
return decorator
@get_decorator('+')
def output():
print('hello world')
output()
上述代码中,由于此前的语法糖语句:@decorator形式是固定的,装饰器decorator()的写法也是固定的,故不能通过改变装饰器decorator()来实现功能。此处采用编写一个新的函数get_decorator()来实现接收参数的功能,并将此前的decorator()装饰器进行返回,使得get_decorator()也成为一个装饰器;并在语法糖语句中传入参数,即可实现要求的功能。
视频讲解链接:https://www.bilibili.com/video/BV12b411g7G7?p=157