装饰器本质就是一个闭包,配合语法糖就构成了一个装饰器。
下面来分析一下多个装饰器执行顺序:
def c(func):
print('c', func)
def inner_c():
print('cc')
print(func)
func()
print('ccc')
return inner_c
def b(func):
print('b', func)
#@functools.wraps(func)
def inner_b():
print('bb')
print(func)
func()
print('bbb')
return inner_b
@c
@b
def a():
print('a')
a= a() #相当于a = c(b(a))()
输出结果:
b <function a at 0x0000022B38B4D400>
c <function b.<locals>.inner_b at 0x0000022B38B4D488>
cc
<function b.<locals>.inner_b at 0x0000022B38B4D488>
bb
<function a at 0x0000022B38B4D400>
a
bbb
ccc
分析:
- b <function a at 0x0000022B38B4D400> :函数a先被装饰器b装饰,说明谁越靠近被装饰的函数,那么谁先被执行
- c <function b..inner_b at 0x0000022B38B4D488>:注意在c装饰器中,打印的是inner_b的函数地址,因为b装饰器执行完成后,将inner_b返回,这个返回值则作为装饰器c的参数传递给c。这也印证了函数被装饰器修饰后,函数名字会发生改变,解决办法可以用@functools.wraps(func)修饰。(即使函数a不被调用,以上两步仍然会被执行)
- cc:函数a依次被b、c装饰器修饰后,会返回inner_c。此时调用函数a(),相当于触发inner_c的调用,因此会先打印cc
- <function b..inner_b at 0x0000022B38B4D488>:inner_c中的func就是第二步中接收到的参数inner_b,所以在这里会打印inner_b的内存地址
- bb:inner_c中调用了func(),而func正是inner_b的内存地址,此时inner_b被调用,所以打印bb
- <function a at 0x0000022B38B4D400>:inner_b中的func参数则为最开始的a函数
- a:真正执行函数a
- bbb:这里继续执行inner_b
- ccc:inner_c继续执行
结论:
- 装饰器的等价写法:a = c(b(a)),按照这个规则,以上分析过程就非常好理解了
- 装饰器函数在被装饰的函数定义好后立即执行,无论函数是否被调用
- 实际使用中,一般不会在装饰器外层添加功能,尤其是在包含多个装饰器时
- 另附一个有用的小表格:
情形 | “等价公式” |
---|---|
单个无参 | fun=dec(fun) |
单个含参 | fun=dec(args1,args2)(fun) |
多个无参 | fun=dec1(dec2(fun)) |
多个含参 | fun=dec1(args1,args2)(dec2(args1,args2)(fun)) |
实际案例分析:
- 登录验证
进入视图函数前,需要增加自定义验证(比如判断是否为空,是否包含危险字符等),涉及两个装饰器:
@login_required
@custom_login_required
我希望在进入系统验证器之前先做自定义验证
像这种都是在被装饰函数执行之前添加额外功能的装饰器谁上谁下呢?
结论是自定义的@custom_login_required在上,系统的@login_required在下。原因可以参考上面的装饰器执行顺序分析。
也就是说,多个装饰器的功能都是在被装饰的函数执行之前添加额外功能,那么越靠上的装饰器,它的功能就越先被执行。
def login_required(func):
def inner():
#一般这里是验证逻辑
...
func()
return inner
def custom_login_required(func):
def inner():
#这里添加自定义验证逻辑
...
func()
return inner
@custom_login_required
@login_required
def index(request):
...
等价于index = custom_login_required(login_required(index))
- 登出后写入log日志及清除session
我现在有两个装饰器:
@clean_session:功能是用户登出后,清除session。
@logout_log:功能是用户登出后,记录log日志。
我的需求是先清除session,在记录日志。
像这种都是在被装饰函数执行之后添加额外功能的装饰器谁上谁下呢?
结论是@login_log在上,@clean_session在下。
也就是说,多个装饰器的功能都是在被装饰的函数执行之后添加额外功能,那么越靠上的装饰器,它的功能就越晚被执行。
def clean_session(func):
def inner():
...
logout_successed = func()
if logout_successed:
#清除session
...
return inner
def logout_log(func):
def inner():
logout_successed = func()
if logout_successed:
log.write(message)
return inner
@logout_log
@clean_session
def index(request):
...
等价于index = logout_log(clean_session(index))
- 相互依赖的装饰器
@decorator1 依赖 @decorator2,比如一个变量context是在@decorator2中创建或者维护的,而@decorator1中需要要使用这个维护后的变量,此时,就构成了依赖关系。
此时谁上谁下呢?
这个得具体问题具体分析,得看context在decorator2中什么时机被维护的,以及在decorator1中什么时候被使用的。
实际开发中,这种有依赖关系的装饰器应该很少吧。
- 当系统装饰器和你自定义的装饰器共存时
比如你想在包含@require_http_methods或@permission_required装饰器的django视图上添加自定义的装饰器,一般情况下你自定义的装饰器要放在最下面,并且你自定义的装饰器中的功能不应该依赖系统装饰器,也就说无论有无系统装饰器,你自定义的装饰器都能正常工作,这一点非常重要!!!!
所以说,多个装饰器谁在上谁在下,还得根据具体的业务和代码而定啊!!!
————————————————
版权声明:本文为CSDN博主「蓝绿色~菠菜」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/bocai_xiaodaidai/article/details/112539898