1.什么是装饰器
装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的
前提下增加额外的功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,
比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景。
首先我们先了解以下通用装饰器的框架,帮助我们更好地理解装饰器的用法
def log(func): ##接收一个函数作为参数,传入的实参就是被装饰的函数
def wrapper(*arg,**kwargs): #这里的形参表示可以接收任意类型的参数
print('函数%s被调用了' % func.__name__) #为被调用的函数添加额外的功能
return func(*args,**kwargs) #调用被装饰的函数,即返回被装饰函数的执行结果
return wrapper #返回函数对象
@log ##语法糖
def add(sum1,sum2):
print(sum1,sum2)
return sum1+sum2
add(1,2)
关于@log的用法:
@log被成为语法糖,作用是把装饰器应用到被装饰的函数,相当于执行了语句add =log(add)。
首先被装饰的函数add作为实参传递给装饰器log,装饰器log返回的是函数对象wrapper,然后将wrapper赋值给名为add的变量,最后调用add(1,2)实际是执行wrapper(1,2)。
在这里我们写一个装饰器的实际应用的例子:
import time
age = 19
def is_adult(fun):
def wrapper(): #因为被装饰的函数没有传递参数,此处可以不写*args,**kwargs
start_time = time.time #增加时间模块,在执行被修饰的函数的同时打印出程序运行的时间
if age>=18:
fun() #由于被装饰的函数不传递参数,直接打印出print的内容,所以不需要return
else:
print('未成年')
end_time = time.time
print('程序运行时间:%.3f ' %(end_time))
return wrapper
@is_adult
def music():
time.sleep(1)
print('正在听音乐.....')
@is_adult
def videos():
print('正在看视频.....')
@is_adult
def game():
print('正在玩游戏......')
music()
videos()
game()
下面我们详细理解装饰器函数的运行过程:
1.首先我们定义了一个装饰器函数is_adult(fun),判断age是否大于18,如果大于18,则执行被装饰的函数,被装饰的函数就是给该函数传递的实参就是下面的三个函数music(),videos(),game(),这三个函数不传递参数,直接打印print后面的内容。
2.我们令age=19,程序在执行的时候,if语句判断age大于18,则执行fun(),此处的fun()即就是传入is_adult(fun)的实参music,执行fun()即执行被修饰函数music(),执行结果为打印print中的内容,但这个结果不会直接显示除了,因为is_adult(fun)返回一个函数对象,我们接收到一个函数对象wrapper。
3.由于有语法糖@is_adult,语法糖的作用就相当于执行了music = is_adult(music),括号中的music即为 is_adult(fun)对应的实参,即就是被装饰函数music(),is_adult(music)返回一个函数对象wrapper,我们定义名为music的变量(等号左边的music)接收这个函数对象,最后执行music(),实际是执行wrapper(),程序执行结束,得到结果。
2.多个装饰器
我们可以用多个装饰器装饰同一个函数,下面我们用一个例子说明同一个函数被多个装饰器装饰时程序的执行过程:
def is_login(fun):
def wrapper1(*args, **kwargs):
print("判断是否登录......")
result = fun(*args, **kwargs)
return result
return wrapper1
def is_permission(fun):
def wrapper2(*args, **kwargs):
print("判断是否有权限......")
result = fun(*args, **kwargs)
return result
return wrapper2
@is_login
@is_permission
def delete():
return "正在删除学生信息"
result = delete()
print(result)
##执行结果为
判断是否登录......
判断是否有权限......
正在删除学生信息
程序执行过程:
从执行结果可以看到,程序执行时先执行了装饰器is_login(fun),然后执行装饰器is_permission(fun),即先执行上面的装饰器,再执行下面的装饰器。
装饰器装饰过程:
但装饰器对函数的装饰过程并非程序的执行顺序。
被装饰的函数delete()先被装饰器is_permission()装饰,该装饰器返回一个函数对象wrapper2,然后该函数对象作为实参传递给装饰器is_login(),返回一个函数对象warpper1,执行语法糖后warpper1函数对象赋值给delete变量(该过程与一个装饰器装饰过程相同),最后执行delete(),得到结果。
对于多个装饰器装饰的函数,我们可以理解为下面的装饰器先装饰要被装饰的函数(例如is_permission先装饰delete),然后上面的装饰器再装饰这个已经装饰过的整体(即is_login装饰is_permission装饰后的整体),如果还有更多的装饰器,可以理解为不断迭代装饰,最后执行的时候从上到下嵌套执行。
3.带参数的装饰器
如果装饰器需要传递参数, 在原有的装饰器外面嵌套一个函数即可。
例如:
def auth(type):
def wrapper1(fun):
def wrapper(*args, **kwargs):
if type=='local':
user = input("User:")
passwd = input("Passwd:")
if user == 'root' and passwd == 'westos':
result = fun(*args, **kwargs)
return result
else:
print("用户名/密码错误")
else:
print("暂不支持远程用户登录")
return wrapper
return wrapper1
@auth(type='remote')
def home():
print("这是主页")
home()
程序执行过程:
在这个程序中可以看到在原有的装饰器warpper1外面嵌套了一个函数auth(type),这样就可以把type参数传递到装饰器中,我们主要理解这个时候语法糖时如何使用的。
如果我们不传递参数的话,此处的语法糖应该时@warpper1,这样便给home()函数加上了装饰器,现在需要传递参数,语法糖变为@auth(type=‘remote’),因为auth(type)函数的返回值为wrapper1的函数对象,所以执行语法糖@auth(type=‘remote’)实际上时在执行语法糖@wrapper1,实际上与之前不穿参数的语法糖时一样的。