装饰器的作用就是在不改变原函数的基础上,动态的扩展函数的功能。
装饰器本质就是返回一个函数的高阶函数,那么什么是高阶函数?一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。举个栗子:
比如我们现在有一个执行登录的函数login(),但是在登录之前我们需要一个验证函数validate()对用户进行验证。我们可以这么写:
def validate(func):
print("进行验证")
func()
def login():
print("用户登录")
validate(login)
##运行结果:
进行验证
用户登录
此处validate函数就是一个高阶函数。但是这么写,我们虽然没有对原函数进行修改,但是我们调用函数的时候需要这么调用:validate(login),当有很多函数都需要验证时,我们需要修改所有函数的调用接口。那能不能在不改变原来的调用方式,调用login()时就能实现上面的功能呢?当然可以,请看下面:
1.最简单的装饰器
def validate(func):
def wrapper():
print("进行验证")
func()
return wrapper
@validate
def login():
print("用户登录")
login()
##运行结果:
进行验证
用户登录
这样我们就实现上面的需求了,这是一个最简单的装饰器的例子。
当我们在login函数上面加上@validate时,就完成了函数的装载,相当于执行了validate(login)(外函数)。但是执行validate(login)只是返回了wrapper函数(内函数),将wrapper指向的函数赋值给了login。值得注意的是:我们可以将函数名看成是变量,它指向真正的函数对象。当我们再调用login函数时,实际执行的是wrapper函数。
## 装饰器会改变原函数的一些属性值,例如__name__属性值
def validate(func):
def wrapper():
print("进行验证")
func()
return wrapper
@validate
def login():
print("用户登录")
print(login.__name__)
##运行结果:
wrapper
解决办法:python为我们提供了一个装饰器functools.wraps。改进后的代码:
from functools import wraps
def validate(func):
@wraps(func)
def wrapper():
print("进行验证")
func()
return wrapper
@validate
def login():
print("用户登录")
print(login.__name__)
## 运行结果:
login
2.兼容被装饰函数的参数和返回值(完整)
from functools import wraps
def validate(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("进行验证")
res = func(*args, **kwargs)
return res
return wrapper
@validate
def login(auth):
print("用户登录: %s" %(auth))
return "haha"
res = login("vip")
print(res)
## 运行结果:
进行验证
用户登录: vip
3.带参数的装饰器
from functools import wraps
def validate(auth):
def outter_wrapper(func):
@wraps(func)
def inner_wrapper(*args, **kwargs):
if auth == "vip":
print("进行登录验证")
func(*args, **kwargs)
else:
print("权限不够")
return inner_wrapper
return outter_wrapper
@validate("not_vip") # vip
def login():
print("用户登录")
login()
## 运行结果
权限不够
函数声明时就会完成装饰器的装载,相当于login=validate("not_vip").(login)。
4.多个装饰器
from functools import wraps
def dec01(func):
print("1111")
@wraps(func)
def wrapper01():
print("2222")
func()
print("3333")
return wrapper01
def dec02(func):
print("aaaa")
@wraps(func)
def wrapper02():
print("bbbb")
func()
print("cccc")
return wrapper02
@dec01
@dec02
def test():
print("hell oword")
test()
## 运行结果:
aaaa
1111
2222
bbbb
hell oword
cccc
3333
当有多个函数时,函数的装载顺序是从下到上,即离函数最近的装饰器最先进行函数的装载。
运行结果分析:在声明函数时,装饰器就会进行函数的装载。首先是装饰器dec02进行装载,装载时会执行外函数,即打印”aaaa“,此时test=dec02(test),dec02(test)会返回wrapper02,即test=wrapper02。然后是dec01进行装载,同dec02一样,装载时会执行外函数,即打印”1111“,此时test=dec01(wrapper02),dec01(wrapper02)会返回wrapper01,即test=wrapper01。所以,当调用test函数时,可以将test函数内部结构看成是这样