python多层装饰器的执行顺序分析

装饰器本质就是一个闭包,配合语法糖就构成了一个装饰器。

下面来分析一下多个装饰器执行顺序:

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

分析:

  1. b <function a at 0x0000022B38B4D400> :函数a先被装饰器b装饰,说明谁越靠近被装饰的函数,那么谁先被执行
  2. c <function b.<locals>.inner_b at 0x0000022B38B4D488>:注意在c装饰器中,打印的是inner_b的函数地址,因为b装饰器执行完成后,将inner_b返回,这个返回值则作为装饰器c的参数传递给c。这也印证了函数被装饰器修饰后,函数名字会发生改变,解决办法可以用@functools.wraps(func)修饰。(即使函数a不被调用,以上两步仍然会被执行)
  3. cc:函数a依次被b、c装饰器修饰后,会返回inner_c。此时调用函数a(),相当于触发inner_c的调用,因此会先打印cc
  4. <function b.<locals>.inner_b at 0x0000022B38B4D488>:inner_c中的func就是第二步中接收到的参数inner_b,所以在这里会打印inner_b的内存地址
  5. bb:inner_c中调用了func(),而func正是inner_b的内存地址,此时inner_b被调用,所以打印bb
  6. <function a at 0x0000022B38B4D400>:inner_b中的func参数则为最开始的a函数
  7. a:真正执行函数a
  8. bbb:这里继续执行inner_b
  9. ccc:inner_c继续执行

结论:

  1. 装饰器的等价写法:a = c(b(a)),按照这个规则,以上分析过程就非常好理解了
  2. 装饰器函数在被装饰的函数定义好后立即执行,无论函数是否被调用
  3. 实际使用中,一般不会在装饰器外层添加功能,尤其是在包含多个装饰器时
  4. 另附一个有用的小表格:
情形“等价公式”
单个无参fun=dec(fun)
单个含参fun=dec(args1,args2)(fun)
多个无参fun=dec1(dec2(fun))
多个含参fun=dec1(args1,args2)(dec2(args1,args2)(fun))

 

实际案例分析:

1、登录验证

进入视图函数前,需要增加自定义验证(比如判断是否为空,是否包含危险字符等),涉及两个装饰器:

@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))

2、登出后写入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))

3、相互依赖的装饰器

@decorator1 依赖 @decorator2,比如一个变量context是在@decorator2中创建或者维护的,而@decorator1中需要要使用这个维护后的变量,此时,就构成了依赖关系。

此时谁上谁下呢?

这个得具体问题具体分析,得看context在decorator2中什么时机被维护的,以及在decorator1中什么时候被使用的。

实际开发中,这种有依赖关系的装饰器应该很少吧。

4、当系统装饰器和你自定义的装饰器共存时

比如你想在包含@require_http_methods或@permission_required装饰器的django视图上添加自定义的装饰器,一般情况下你自定义的装饰器要放在最下面,并且你自定义的装饰器中的功能不应该依赖系统装饰器,也就说无论有无系统装饰器,你自定义的装饰器都能正常工作,这一点非常重要!!!!

所以说,多个装饰器谁在上谁在下,还得根据具体的业务和代码而定啊!!!

 

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值