资讯专栏INFORMATION COLUMN

Python 装饰器执行顺序迷思

frolc / 1715人阅读

摘要:探究多个装饰器执行顺序装饰器是用于封装函数或代码的工具,网上可以搜到很多文章可以学习,我在这里要讨论的是多个装饰器执行顺序的一个迷思。这时候你该知道为什么输出结果会是那样,以及对装饰器执行顺序实际发生了什么有一定了解了吧。

探究多个装饰器执行顺序

装饰器是Python用于封装函数或代码的工具,网上可以搜到很多文章可以学习,我在这里要讨论的是多个装饰器执行顺序的一个迷思。

疑问

大部分涉及多个装饰器装饰的函数调用顺序时都会说明它们是自上而下的,比如下面这个例子:

def decorator_a(func):
    print "Get in decorator_a"
    def inner_a(*args, **kwargs):
        print "Get in inner_a"
        return func(*args, **kwargs)
    return inner_a

def decorator_b(func):
    print "Get in decorator_b"
    def inner_b(*args, **kwargs):
        print "Get in inner_b"
        return func(*args, **kwargs)
    return inner_b

@decorator_b
@decorator_a
def f(x):
    print "Get in f"
    return x * 2

f(1)

上面代码先定义里两个函数: decotator_a, decotator_b, 这两个函数实现的功能是,接收一个函数作为参数然后返回创建的另一个函数,在这个创建的函数里调用接收的函数(文字比代码绕人)。最后定义的函数 f 采用上面定义的 decotator_a, decotator_b 作为装饰函数。在当我们以1为参数调用装饰后的函数 f 后, decotator_a, decotator_b 的顺序是什么呢(这里为了表示函数执行的先后顺序,采用打印输出的方式来查看函数的执行顺序)?

如果不假思索根据自下而上的原则来判断地话,先执行 decorator_a 再执行 decorator_b , 那么会先输出 Get in decotator_a, Get in inner_a 再输出 Get in decotator_b , Get in inner_b 。然而事实并非如此。

实际上运行的结果如下:

Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f
函数和函数调用的区别

为什么是先执行 inner_b 再执行 inner_a 呢?为了彻底看清上面的问题,得先分清两个概念:函数和函数调用。上面的例子中 f 称之为函数, f(1) 称之为函数调用,后者是对前者传入参数进行求值的结果。在Python中函数也是一个对象,所以 f 是指代一个函数对象,它的值是函数本身, f(1) 是对函数的调用,它的值是调用的结果,这里的定义下 f(1) 的值2。同样地,拿上面的 decorator_a 函数来说,它返回的是个函数对象 inner_a ,这个函数对象是它内部定义的。在 inner_a 里调用了函数 func ,将 func 的调用结果作为值返回。

装饰器函数在被装饰函数定义好后立即执行

其次得理清的一个问题是,当装饰器装饰一个函数时,究竟发生了什么。现在简化我们的例子,假设是下面这样的:

def decorator_a(func):
    print "Get in decorator_a"
    def inner_a(*args, **kwargs):
        print "Get in inner_a"
        return func(*args, **kwargs)
    return inner_a

@decorator_a
def f(x):
    print "Get in f"
    return x * 2

正如很多介绍装饰器的文章里所说:

@decorator_a
def f(x):
    print "Get in f"
    return x * 2

# 相当于
def f(x):
    print "Get in f"
    return x * 2

f = decorator_a(f)

所以,当解释器执行这段代码时, decorator_a 已经调用了,它以函数 f 作为参数, 返回它内部生成的一个函数,所以此后 f 指代的是 decorater_a 里面返回的 inner_a 。所以当以后调用 f 时,实际上相当于调用 inner_a ,传给 f 的参数会传给 inner_a , 在调用 inner_a 时会把接收到的参数传给 inner_a 里的 funcf ,最后返回的是 f 调用的值,所以在最外面看起来就像直接再调用 f 一样。

疑问的解释

当理清上面两方面概念时,就可以清楚地看清最原始的例子中发生了什么。
当解释器执行下面这段代码时,实际上按照从下到上的顺序已经依次调用了 decorator_adecorator_b ,这是会输出对应的 Get in decorator_aGet in decorator_b 。 这时候 f 已经相当于 decorator_b 里的 inner_b 。但因为 f 并没有被调用,所以 inner_b 并没有调用,依次类推 inner_b 内部的 inner_a 也没有调用,所以 Get in inner_aGet in inner_b 也不会被输出。

@decorator_b
@decorator_a
def f(x):
    print "Get in f"
    return x * 2

然后最后一行当我们对 f 传入参数1进行调用时, inner_b 被调用了,它会先打印 Get in inner_b ,然后在 inner_b 内部调用了 inner_a 所以会再打印 Get in inner_a, 然后再 inner_a 内部调用的原来的 f, 并且将结果作为最终的返回。这时候你该知道为什么输出结果会是那样,以及对装饰器执行顺序实际发生了什么有一定了解了吧。

当我们在上面的例子最后一行 f 的调用去掉,放到repl里演示,也能很自然地看出顺序问题:

➜  test git:(master) ✗ python
Python 2.7.11 (default, Jan 22 2016, 08:29:18)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import test13
Get in decorator_a
Get in decorator_b
>>> test13.f(1)
Get in inner_b
Get in inner_a
Get in f
2
>>> test13.f(2)
Get in inner_b
Get in inner_a
Get in f
4
>>>

在实际应用的场景中,当我们采用上面的方式写了两个装饰方法比如先验证有没有登录 @login_required , 再验证权限够不够时 @permision_allowed 时,我们采用下面的顺序来装饰函数:

@login_required
@permision_allowed
def f()
  # Do something
  return
参考资料

我的大脑和好奇心

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38292.html

相关文章

  • python 多个装饰的调用顺序

    摘要:如果不使用装饰器的话,普通的做法可能是在中写一堆校验代码来判断用户是否登录,然后决定后面的执行逻辑,这样比较麻烦。 前言 装饰器是程序开发中经常会用到的一个功能,也是python语言开发的基础知识,如果能够在程序中合理的使用装饰器,不仅可以提高开发效率,而且可以让写的代码看上去显的高大上^_^ 使用场景 可以用到装饰器的地方有很多,简单的举例如以下场景 引入日志 函数执行时间统计 执...

    wapeyang 评论0 收藏0
  • Python装饰-装饰流程,执行顺序

    摘要:最近看到一个关于的题文章其中的一个是装饰器的顺序问题就想写篇博客回顾下装饰器首先强烈推荐很久之前看的一篇博文翻译理解中的装饰器关于什么是装饰器看这篇文章就好了这里主要想写关于多个装饰器的执行流程装饰顺序示例代码初始化初始化输出结果初始化初始 最近看到一个关于Flask的CTF(RealWorld CTF 2018 web题bookhub)文章其中的一个trick是装饰器的顺序问题,就想...

    cpupro 评论0 收藏0
  • python中多个装饰执行顺序

    今天讲一下python中装饰器的执行顺序,以两个装饰器为例。 装饰器代码如下: def wrapper_out1(func): print(--out11--) def inner1(*args, **kwargs): print(--in11--) ret = func(*args, **kwargs) print(--in12-...

    or0fun 评论0 收藏0
  • Python - 装饰decorator

    摘要:在这种代码运行期间动态增加功能的方式,称之为装饰器。四接收特定类型参数的装饰器装饰器可以接收参数,当调用装饰器返回的函数时,也就调用了包裹函数,把参数传入包裹函数,它将参数传递给被装饰的函数。执行结果执行结果 【题外话】心塞塞 心情down down down 有段时间没用装饰器了,然后然后问着就跪了~~~回来翻了翻资料和代码...... 一、什么是装饰器 装饰器,decor...

    HackerShell 评论0 收藏0
  • Python】一文弄懂python装饰(附源码例子)

    摘要:装饰器的使用符合了面向对象编程的开放封闭原则。三简单的装饰器基于上面的函数执行时间的需求,我们就手写一个简单的装饰器进行实现。函数体就是要实现装饰器的内容。类装饰器的实现是调用了类里面的函数。类装饰器的写法比我们装饰器函数的写法更加简单。 目录 前言 一、什么是装饰器 二、为什么要用装饰器 ...

    liuchengxu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<