举例讲解Python中装饰器的用法


Posted in Python onApril 27, 2015

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。

>>> def now():
...   print '2013-12-25'
...
>>> f = now
>>> f()
2013-12-25

函数对象有一个__name__属性,可以拿到函数的名字:

>>> now.__name__
'now'
>>> f.__name__
'now'

现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

def log(func):
  def wrapper(*args, **kw):
    print 'call %s():' % func.__name__
    return func(*args, **kw)
  return wrapper

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

@log
def now():
  print '2013-12-25'

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now()
call now():
2013-12-25

把@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

def log(text):
  def decorator(func):
    def wrapper(*args, **kw):
      print '%s %s():' % (text, func.__name__)
      return func(*args, **kw)
    return wrapper
  return decorator

这个3层嵌套的decorator用法如下:

@log('execute')
def now():
  print '2013-12-25'

执行结果如下:

>>> now()
execute now():
2013-12-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

>>> now = log('execute')(now)

我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper':

>>> now.__name__
'wrapper'

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

import functools

def log(func):
  @functools.wraps(func)
  def wrapper(*args, **kw):
    print 'call %s():' % func.__name__
    return func(*args, **kw)
  return wrapper

或者针对带参数的decorator:

import functools

def log(text):
  def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
      print '%s %s():' % (text, func.__name__)
      return func(*args, **kw)
    return wrapper
  return decorator

import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。
小结

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

请编写一个decorator,能在函数调用的前后打印出'begin call'和'end call'的日志。

再思考一下能否写出一个@log的decorator,使它既支持:

@log
def f():
  pass

又支持:

@log('execute')
def f():
  pass
Python 相关文章推荐
python生成随机mac地址的方法
Mar 16 Python
使用Python脚本将绝对url替换为相对url的教程
Apr 24 Python
Python实现模拟分割大文件及多线程处理的方法
Oct 10 Python
Python查找两个有序列表中位数的方法【基于归并算法】
Apr 20 Python
Python实现自定义顺序、排列写入数据到Excel的方法
Apr 23 Python
Pandas 合并多个Dataframe(merge,concat)的方法
Jun 08 Python
解决pycharm无法识别本地site-packages的问题
Oct 13 Python
linux环境中没有网络怎么下载python
Jul 07 Python
django自带serializers序列化返回指定字段的方法
Aug 21 Python
django formset实现数据表的批量操作的示例代码
Dec 06 Python
python 绘制国旗的示例
Sep 27 Python
python模板入门教程之flask Jinja
Apr 11 Python
Python中的匿名函数使用简介
Apr 27 #Python
Python中用函数作为返回值和实现闭包的教程
Apr 27 #Python
Python中利用sorted()函数排序的简单教程
Apr 27 #Python
Python中的filter()函数的用法
Apr 27 #Python
Python中的map()函数和reduce()函数的用法
Apr 27 #Python
PyMongo安装使用笔记
Apr 27 #Python
Windows下PyMongo下载及安装教程
Apr 27 #Python
You might like
PHP利用func_get_args和func_num_args函数实现函数重载实例
2014/11/12 PHP
PHP连接access数据库
2015/03/27 PHP
PHP中preg_match函数正则匹配的字符串长度问题
2015/05/27 PHP
extjs 的权限问题 要求控制的对象是 菜单,按钮,URL
2010/03/09 Javascript
jQuery下通过$.browser来判断浏览器.
2011/04/05 Javascript
jquery怎样实现ajax联动框(一)
2013/03/08 Javascript
sogou地图API用法实例教程
2014/09/11 Javascript
SuperSlide标签切换、焦点图多种组合插件
2015/03/14 Javascript
基于JQuery实现仿网易邮箱全屏动感滚动插件fullPage
2015/09/20 Javascript
原生JavaScript实现瀑布流布局
2020/06/28 Javascript
js实现简单排列组合的方法
2016/01/27 Javascript
bootstrap jquery dataTable 异步ajax刷新表格数据的实现方法
2017/02/10 Javascript
vue实现一个移动端屏蔽滑动的遮罩层实例
2017/06/08 Javascript
vue一个页面实现音乐播放器的示例
2018/02/06 Javascript
jQuery使用动画队列自定义动画操作示例
2018/06/16 jQuery
vue生成token并保存到本地存储中
2018/07/17 Javascript
jQuery实现图片下载代码
2019/07/18 jQuery
vue npm install 安装某个指定的版本操作
2020/08/11 Javascript
python生成日历实例解析
2014/08/21 Python
python通过colorama模块在控制台输出彩色文字的方法
2015/03/19 Python
Python中第三方库Requests库的高级用法详解
2017/03/12 Python
在IPython中进行Python程序执行时间的测量方法
2018/11/01 Python
Python3.5迭代器与生成器用法实例分析
2019/04/30 Python
python 读取更新中的log 或其它文本方式
2019/12/24 Python
简单了解python filter、map、reduce的区别
2020/01/14 Python
django-orm F对象的使用 按照两个字段的和,乘积排序实例
2020/05/18 Python
Python类的继承super相关原理解析
2020/10/22 Python
selenium判断元素是否存在的两种方法小结
2020/12/07 Python
sort命令的作用和用法
2012/11/04 面试题
有关打架的检讨书
2014/01/25 职场文书
竞聘演讲稿怎么写
2014/08/28 职场文书
政协会议宣传标语
2014/10/09 职场文书
党员干部作风建设思想汇报范文
2014/10/25 职场文书
给校长的建议书作文500字
2015/09/14 职场文书
详解Java实现数据结构之并查集
2021/06/23 Java/Android
浅谈mysql哪些情况会导致索引失效
2021/11/20 MySQL