详解Python 装饰器执行顺序迷思


Posted in Python onAugust 08, 2018

探究多个装饰器执行顺序

装饰器是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里隐藏的“禅”
Jun 16 Python
离线安装Pyecharts的步骤以及依赖包流程
Apr 23 Python
Python cookbook(数据结构与算法)实现优先级队列的方法示例
Feb 18 Python
解决安装tensorflow遇到无法卸载numpy 1.8.0rc1的问题
Jun 13 Python
python画柱状图--不同颜色并显示数值的方法
Dec 13 Python
对python产生随机的二维数组实例详解
Dec 13 Python
python dict 相同key 合并value的实例
Jan 21 Python
Python控制Firefox方法总结
Jun 03 Python
使用python的turtle函数绘制一个滑稽表情
Feb 28 Python
Xadmin+rules实现多选行权限方式(级联效果)
Apr 07 Python
MoviePy简介及Python视频剪辑自动化
Dec 18 Python
Python opencv缺陷检测的实现及问题解决
Apr 24 Python
python Flask 装饰器顺序问题解决
Aug 08 #Python
Python BS4库的安装与使用详解
Aug 08 #Python
python特性语法之遍历、公共方法、引用
Aug 08 #Python
用Python shell简化开发
Aug 08 #Python
在Python中使用gRPC的方法示例
Aug 08 #Python
Python实现购物评论文本情感分析操作【基于中文文本挖掘库snownlp】
Aug 07 #Python
python实现彩票系统
Jun 28 #Python
You might like
PHP分享图片的生成方法
2018/04/25 PHP
有关DOM元素与事件的3个谜题
2010/11/11 Javascript
基于JQuery的asp.net树实现代码
2010/11/30 Javascript
javascript学习基础笔记之DOM对象操作
2011/11/03 Javascript
js实现的仿新浪微博完美的时间组件升级版
2011/12/20 Javascript
javascript 兼容所有浏览器的DOM扩展功能
2012/08/01 Javascript
JS操作Cookies包括(读取添加与删除)
2012/12/26 Javascript
JavaScript实现在数组中查找不同顺序排列的字符串
2014/09/26 Javascript
JS输入用户名自动显示邮箱后缀列表的方法
2015/01/27 Javascript
js实现多选项切换导航菜单的方法
2015/02/06 Javascript
JavaScript实现定时隐藏与显示图片的方法
2015/08/06 Javascript
探索angularjs+requirejs全面实现按需加载的套路
2016/02/26 Javascript
javascript 中的console.log和弹出窗口alert
2016/08/30 Javascript
jquery datatable服务端分页
2016/08/31 Javascript
JavaScript-html标题滚动效果的简单实现
2016/09/08 Javascript
Javascript实现时间倒计时功能
2018/11/17 Javascript
jQuery+ThinkPHP实现图片上传
2020/07/23 jQuery
[06:57]DOTA2-DPC中国联赛 正赛 Ehome vs PSG.LGD 选手采访
2021/03/11 DOTA
Python合并多个装饰器小技巧
2015/04/28 Python
Python实现快速计算词频功能示例
2018/06/25 Python
python中的协程深入理解
2019/06/10 Python
python编程进阶之异常处理用法实例分析
2020/02/21 Python
使用Keras预训练模型ResNet50进行图像分类方式
2020/05/23 Python
python 邮件检测工具mmpi的使用
2021/01/04 Python
基础的CSS3弹性盒Flexbox布局使用实例
2016/04/08 HTML / CSS
巴基斯坦电子产品购物网站:Home Shopping
2017/09/14 全球购物
澳大利亚排名第一的露营和户外设备在线零售商:Outbax
2020/05/06 全球购物
焊接专业毕业生求职信
2013/10/01 职场文书
大学生素质拓展活动方案
2014/02/11 职场文书
大学毕业生求职自荐书
2014/06/05 职场文书
群众路线教育实践活动调研报告
2014/11/03 职场文书
2015年电教工作总结
2015/05/26 职场文书
新学期开学寄语2016
2015/12/04 职场文书
某药房的新员工入职告知书!
2019/07/15 职场文书
Pytorch数据读取之Dataset和DataLoader知识总结
2021/05/23 Python
css常用字体属性与背景属性介绍
2022/02/28 HTML / CSS