Python装饰器执行顺序详解
探究多个装饰器执行顺序
装饰器是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 里的 func 即 f ,最后返回的是 f 调用的值,所以在最外面看起来就像直接再调用 f 一样。
疑问的解释
当理清上面两方面概念时,就可以清楚地看清最原始的例子中发生了什么。
当解释器执行下面这段代码时,实际上按照从下到上的顺序已经依次调用了 decorator_a 和 decorator_b ,这是会输出对应的 Get in decorator_a 和 Get in decorator_b 。 这时候 f 已经相当于 decorator_b 里的 inner_b 。但因为 f 并没有被调用,所以 inner_b 并没有调用,依次类推 inner_b 内部的 inner_a 也没有调用,所以 Get in inner_a 和 Get 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装饰器执行顺序详解的更多相关文章
- Python 装饰器执行顺序
Python 装饰器执行顺序 之前同事问到两个装饰器在代码中使用顺序不同会不会有什么问题,装饰器是对被装饰的函数做了一层包装,然后执行的时候执行了被包装后的函数,例如: def decorator_a ...
- Python 装饰器执行顺序迷思
Table of Contents 1. 探究多个装饰器执行顺序 1.1. 疑问 1.2. 函数和函数调用的区别 1.3. 装饰器函数在被装饰函数定义好后立即执行 1.4. 疑问的解释 2. 参考资料 ...
- python装饰器执行顺序
. python 装饰器 1) 2层装饰器 def decorator(func): # TODO def wrapper(*args, **kwargs): # TODO func(*args, * ...
- Python面试题之多个装饰器执行顺序
疑问 大部分涉及多个装饰器装饰的函数调用顺序时都会说明它们是自上而下的,比如下面这个例子: def decorator_a(func): print 'Get in decorator_a' def ...
- (转)Python3.5——装饰器及应用详解
原文:https://blog.csdn.net/loveliuzz/article/details/77853346 Python3.5——装饰器及应用详解(下)----https://blog.c ...
- Unity 继承MonoBehaviour脚本 执行顺序 详解
先看结果 Awake ->OnEnable-> Start ->-> FixedUpdate-> Update -> LateUpdate ->OnGUI ...
- javascript运行机制之执行顺序详解(转)
转自http://www.admin10000.com/document/3385.html JavaScript是一种描述型脚本语言,它不同于java或C#等编译性语言,它不需要进行编译成中间语言, ...
- 【Node.js】Event Loop执行顺序详解
本文基于node 0.10.22版本 关于EventLoop是什么,请看阮老师写的什么是EventLoop 本文讲述的是EventLoop中的执行顺序(着重讲setImmediate, setTime ...
- javascript 执行顺序详解
JavaScript是一种描述 型脚本语言,它不同于java或C#等编译性语言,它不需要进行编译成中间语言,而是由浏览器进行动态地解析与执行.如果你不能理解javaScript 语言的运行机制,或者简 ...
随机推荐
- xgboost安装 win10 64位 annaconda
重点参考三个网站: https://www.ibm.com/developerworks/community/blogs/jfp/entry/Installing_XGBoost_For_Anacon ...
- java 弹出选择目录框(选择文件夹),获取选择的文件夹路径
java 弹出选择目录框(选择文件夹),获取选择的文件夹路径 java 弹出选择目录框(选择文件夹),获取选择的文件夹路径:int result = 0;File file = null;String ...
- UOJ #276「清华集训2016」汽水
为什么你们常数都这么小啊 UOJ #276 题意:在树上找一条链使得|边权平均值$ -k$|尽量小,$ n<=5e4$ $ Solution:$ 首先二分答案$ ans$,即我们需要找一条链使得 ...
- MIME 类型
关于读音 为了防止大家出去丢人,我先示范一下,MIME应该独坐[maim],听起来就好像“男人”的英语法人一样. 浏览器和MIME的关系 浏览器依靠MIME类型解释网页. 每当浏览器请求一个web页面 ...
- 小程序view排版
<view class="bc"> <view> <text bc_text>demo</text> </view> & ...
- Tip:HttpServletRequest
HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,开发人员通过这个对象的方法,可以获得客户这些信息. Tip ...
- Java-Servlet--《12-WEB应用中的普通Java程序如何读取资源文件.mp4》 有疑问
\第五天-servlet开发和ServletConfig与ServletContext对象\12-WEB应用中的普通Java程序如何读取资源文件.mp4; 多层时,DAO为了得到资源文件中的配置参数: ...
- 下面哪些是无效的Java标识?为什么?
下面哪些是无效的Java标识?为什么? a.Factorial b.anExtremelyLongIdebtifierifyouAsKMe c.2ndLevel d.level2 e.MAX_SIZE ...
- dubbo初步认识
dubbo中文网站:http://dubbo.apache.org/zh-cn/ dubbo英文网站:http://dubbo.apache.org/en-us/ 1.Apache Dubbo是一款高 ...
- Eclipse打印GC日志
一.生成gc.log 第一步:右键项目或文件——Run As——Run Configurations. 第二步:点击Arguments,在VM arguments中填写-Xloggc:F:/gc.lo ...