疑问

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

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

参考

Python面试题之多个装饰器执行顺序的更多相关文章

  1. Python装饰器执行顺序详解

    探究多个装饰器执行顺序 装饰器是Python用于封装函数或代码的工具,网上可以搜到很多文章可以学习,我在这里要讨论的是多个装饰器执行顺序的一个迷思. 疑问 大部分涉及多个装饰器装饰的函数调用顺序时都会 ...

  2. Python 装饰器执行顺序迷思

    Table of Contents 1. 探究多个装饰器执行顺序 1.1. 疑问 1.2. 函数和函数调用的区别 1.3. 装饰器函数在被装饰函数定义好后立即执行 1.4. 疑问的解释 2. 参考资料 ...

  3. Python 装饰器执行顺序

    Python 装饰器执行顺序 之前同事问到两个装饰器在代码中使用顺序不同会不会有什么问题,装饰器是对被装饰的函数做了一层包装,然后执行的时候执行了被包装后的函数,例如: def decorator_a ...

  4. python装饰器执行顺序

    . python 装饰器 1) 2层装饰器 def decorator(func): # TODO def wrapper(*args, **kwargs): # TODO func(*args, * ...

  5. 【Todo】Python面试题分析记录(修饰器等)

    首先,看这一段代码: class A(object): x = 1 gen = (lambda t: (t for _ in xrange(10)))(x) if __name__ == '__mai ...

  6. Python之命名空间、闭包、装饰器

    一.命名空间 1. 命名空间 命名空间是一个字典,key是变量名(包括函数.模块.变量等),value是变量的值. 2. 命名空间的种类和查找顺序 - 局部命名空间:当前函数 - 全局命名空间:当前模 ...

  7. Python基础(七) python自带的三个装饰器

    说到装饰器,就不得不说python自带的三个装饰器: 1.@property   将某函数,做为属性使用 @property 修饰,就是将方法,变成一个属性来使用. class A(): @prope ...

  8. Python之面向对象:闭包和装饰器

    一.闭包 1. 如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包. def outter(): name='python' def inner(): print na ...

  9. Python菜鸟之路:Python基础-逼格提升利器:装饰器Decorator

    一.装饰器 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等. 装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身 ...

随机推荐

  1. typecho与wordpress

    相信玩博客的人对这两个程序会相当熟悉把. 有些人玩了很久WP之后又转向了typecho 之后又回到wp.举个例子typecho犹如一个美丽的裸体女人.wp就是一个穿着绫罗绸缎的女人. 当你看着裸体一天 ...

  2. 初级Java面试题 – SSM框架篇

    加入我的QQ群(701974765) 获取更多好用又好玩的软件,还有不定期发放的福利呦(- ̄▽ ̄)- Spring的优点/对Spring的理解 Spring的AOP编程 Spring的IOC Spri ...

  3. 170330、Spring中你不知道的注入方式

    前言 在Spring配置文件中使用XML文件进行配置,实际上是让Spring执行了相应的代码,例如: 使用<bean>元素,实际上是让Spring执行无参或有参构造器 使用<prop ...

  4. TCP粘包/拆包 ByteBuf和channel 如果没有Netty? 传统的多线程服务器,这个也是Apache处理请求的模式

    通俗地讲,Netty 能做什么? - 知乎 https://www.zhihu.com/question/24322387 谢邀.netty是一套在java NIO的基础上封装的便于用户开发网络应用程 ...

  5. INFORMATION_SCHEMA.STATISTICS 统计 表 库 大小

    INFORMATION_SCHEMA MySQL :: MySQL 5.5 Reference Manual :: 21 INFORMATION_SCHEMA Tables https://dev.m ...

  6. linux, sysrq,acpi,apci,uio,subsystem daemon

    linux, sysrq,acpi,apci Linux设备模型  一.sysfs文件系统: sysfs文件系统是Linux2.6内核引入的,它被看成是与proc.devfs和devpty等同类别的文 ...

  7. 【react表格组件】react-virtualized虚拟列表

    https://css-tricks.com/rendering-lists-using-react-virtualized/

  8. Robberies---hdu2955(概率dp,01背包)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2955 题目给了每个银行的钱和被抓的概率,由于要抢尽量多的钱,所以要保证尽量不被抓,而抢多个银行之后不被 ...

  9. 12.Project Fields to Return from Query-官方文档摘录

    1 插入例句 db.inventory.insertMany( [ { item: "journal", status: "A", size: { h: 14, ...

  10. 从HD OJ 1005想到的

    杭电OJ [1005](http://acm.hdu.edu.cn/showproblem.php?pid=1005): #####Problem Description > A number ...