Python 装饰器执行顺序迷思
Table of Contents
装饰器迷思值多个装饰器执行顺序
装饰器是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装饰器执行顺序详解
探究多个装饰器执行顺序 装饰器是Python用于封装函数或代码的工具,网上可以搜到很多文章可以学习,我在这里要讨论的是多个装饰器执行顺序的一个迷思. 疑问 大部分涉及多个装饰器装饰的函数调用顺序时都会 ...
- 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 ...
- 粗浅聊聊Python装饰器
浅析装饰器 通常情况下,给一个对象添加新功能有三种方式: 直接给对象所属的类添加方法: 使用组合:(在新类中创建原有类的对象,重复利用已有类的功能) 使用继承:(可以使用现有类的,无需重复编写原有类进 ...
- Python基础篇【第6篇】: Python装饰器
装饰器 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰类, ...
- 理解Python装饰器
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象.它经常用于有切面需求的场景,比如:插入日志.性能测试.事务处理.缓存.权 ...
- Python装饰器由浅入深
装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...
- python装饰器方法
前几天向几位新同事介绍项目,被问起了@login_required的实现,我说这是django框架提供的装饰器方法,验证用户是否登录,只要这样用就行了,因为自己不熟,并没有做过多解释. 今天查看dja ...
随机推荐
- Design Pattern ->Prototype
Layering & Contract Philosophy With additional indirection Prototype The example code is as foll ...
- Touch事件传递的实验
通过自定义的Relayout LinearLayout TextView , 布局为: 分别打印事件方法: 1.当所有的都是super的时候,点击TextView的时候,事件的传递是: ...
- 在unbuntu 1204(32位)下安装hadoop2.2.0的一些问题
虽然在网上可以找到很多这样的step by step的教程,但是我还是遇到了很多问题.趁着一点记忆,将这些问题记录下来.安装过程参考了以下博客: http://www.cnblogs.com/life ...
- tfs2012安装
今天正在配置tfs的服务器.要先安装net 3.5 ps1.要选择安装reportingservers 来启动报表功能.
- c#Winform程序调用app.config文件配置数据库连接字符串
你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings name=" " connectionString= ...
- 洛谷 P1080 国王游戏
题目描述 恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右手上面分别写下一个整数,国王自己也在左.右手上各写一个整数.然后,让这 n 位大臣排成一排,国王站在队伍的最 ...
- 概念:详细讲解url和路由概念
例如:一个网址为 http://www.abc.com/aa 定义:/aa = bb/cc/dd 那么:http://www.abc.com/aa就是一个url,那么我们可以得出:网址=url 而当我 ...
- CMAKE 安装
下载 解压 https://cmake.org/download/ https://cmake.org/files/v3.7/cmake-3.7.1.tar.gz yum install gcc - ...
- IOS instancetype的使用好处
instancetype的类型表示上,跟id一样,可以表示任何对象类型 instancetype只能用在返回值类型上,不能像 id 一样用在参数类型上 instancetype 比 id 多一个好处 ...
- 用keytool制作证书并在tomcat配置https服务(三)
用keytool制作证书并在tomcat配置https服务(一) 用keytool制作证书并在tomcat配置https服务(二) 用keytool制作证书并在tomcat配置https服务(四) 模 ...