详解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的urllib模块显示下载进度示例
Jan 17 Python
Python 爬虫多线程详解及实例代码
Oct 08 Python
python实现人人自动回复、抢沙发功能
Jun 08 Python
在python 不同时区之间的差值与转换方法
Jan 14 Python
python接口自动化(十七)--Json 数据处理---一次爬坑记(详解)
Apr 18 Python
python常用库之NumPy和sklearn入门
Jul 11 Python
pytorch torchvision.ImageFolder的用法介绍
Feb 20 Python
python支持多继承吗
Jun 19 Python
Visual Studio code 配置Python开发环境
Sep 11 Python
python 无损批量压缩图片(支持保留图片信息)的示例
Sep 22 Python
分享一枚pycharm激活码适用所有pycharm版本我的pycharm2020.2.3激活成功
Nov 20 Python
Python Pandas数据分析之iloc和loc的用法详解
Nov 11 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
thinkphp5框架实现数据库读取的数据转换成json格式示例
2019/10/10 PHP
非常不错的一个javascript 类
2006/11/07 Javascript
JavaScript 继承的实现
2009/07/09 Javascript
30个让人兴奋的视差滚动(Parallax Scrolling)效果网站
2012/03/04 Javascript
jQuery动画效果-fadeIn fadeOut淡入浅出示例代码
2013/08/28 Javascript
Json和Jsonp理论实例代码详解
2013/11/15 Javascript
javascript字符串替换及字符串分割示例代码
2013/12/12 Javascript
JS实现点击链接取消跳转效果的方法
2014/01/24 Javascript
jQuery+CSS3实现3D立方体旋转效果
2015/11/10 Javascript
JavaScript 中有关数组对象的方法(详解)
2016/08/15 Javascript
jQuery模拟完美实现经典FLASH导航动画效果【附demo源码下载】
2016/11/09 Javascript
layer实现弹窗提交信息
2016/12/12 Javascript
JS使用正则实现去掉字符串左右空格的方法
2016/12/27 Javascript
JavaScript中利用构造器函数模拟类的方法
2017/02/16 Javascript
Javascript调试之console对象——你不知道的一些小技巧
2017/07/10 Javascript
vue组件横向树实现代码
2018/08/02 Javascript
解决Layui数据表格中checkbox位置不居中的方法
2018/08/15 Javascript
微信小游戏之使用three.js 绘制一个旋转的三角形
2019/06/10 Javascript
[14:57]DOTA2 HEROS教学视频教你分分钟做大人-幽鬼
2014/06/13 DOTA
Python使用zip合并相邻列表项的方法示例
2018/03/17 Python
Pycharm2017版本设置启动时默认自动打开项目的方法
2018/10/29 Python
Python3最长回文子串算法示例
2019/03/04 Python
使用pyinstaller打包PyQt4程序遇到的问题及解决方法
2019/06/24 Python
通过实例解析python描述符原理作用
2020/01/22 Python
Python调用接口合并Excel表代码实例
2020/03/31 Python
html5中地理位置定位api接口开发应用小结
2013/01/04 HTML / CSS
T3官网:头发造型工具
2019/12/26 全球购物
父亲生日宴会答谢词
2014/01/10 职场文书
校园安全教育广播稿
2014/02/17 职场文书
2014年母亲节演讲稿范文
2014/05/07 职场文书
奖金申请报告模板
2015/05/15 职场文书
幼儿园庆元旦主持词
2015/07/06 职场文书
中学教师教学工作总结
2015/08/13 职场文书
MySQL中distinct和count(*)的使用方法比较
2021/05/26 MySQL
使用GO语言实现Mysql数据库CURD的简单示例
2021/08/07 Golang
MySQL令人大跌眼镜的隐式转换
2021/08/23 MySQL