举例讲解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 相关文章推荐
30分钟搭建Python的Flask框架并在上面编写第一个应用
Mar 30 Python
简单介绍Python的轻便web框架Bottle
Apr 08 Python
Python实现大文件排序的方法
Jul 10 Python
Python提取网页中超链接的方法
Sep 18 Python
Python实现自动登录百度空间的方法
Jun 10 Python
python基础while循环及if判断的实例讲解
Aug 25 Python
Django中的Signal代码详解
Feb 05 Python
python之cv2与图像的载入、显示和保存实例
Dec 05 Python
django之自定义软删除Model的方法
Aug 14 Python
Django自带的用户验证系统实现
Dec 18 Python
浅析python实现动态规划背包问题
Dec 31 Python
解决Jupyter-notebook不弹出默认浏览器的问题
Mar 30 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
了解咖啡雨林联盟认证 什么是雨林认证 雨林认证是什么意思
2021/03/05 新手入门
php中判断字符串是否全是中文或含有中文的实现代码
2011/09/16 PHP
php无法连接mysql数据库的正确解决方法
2016/07/01 PHP
php判断是否为ajax请求的方法
2016/11/29 PHP
PHP抽象类和接口用法实例详解
2019/07/20 PHP
PHP重载基础知识回顾
2020/09/10 PHP
ExtJs之带图片的下拉列表框插件
2010/03/04 Javascript
JavaScript实现统计文本框Textarea字数增强用户体验
2012/12/21 Javascript
js时间日期和毫秒的相互转换
2013/02/22 Javascript
深入理解JavaScript系列(34):设计模式之命令模式详解
2015/03/03 Javascript
jQuery实现灰蓝风格标准二级下拉菜单效果代码
2015/08/31 Javascript
jQuery实现点击按钮弹出可关闭层的浮动层插件
2015/09/19 Javascript
jQuery结合CSS制作动态的下拉菜单
2015/10/27 Javascript
浅析vue数据绑定
2017/01/17 Javascript
Node.js使用Express创建Web项目详细教程
2017/03/31 Javascript
使用sessionStorage解决vuex在页面刷新后数据被清除的问题
2018/04/13 Javascript
解决一个微信号同时支持多个环境网页授权问题
2019/08/07 Javascript
讲解python参数和作用域的使用
2013/11/01 Python
python脚本设置系统时间的两种方法
2016/02/21 Python
今天 平安夜 Python 送你一顶圣诞帽 @微信官方
2017/12/25 Python
Python数据分析之双色球统计单个红和蓝球哪个比例高的方法
2018/02/03 Python
Python爬虫包BeautifulSoup实例(三)
2018/06/17 Python
详解如何减少python内存的消耗
2019/08/09 Python
Python 中判断列表是否为空的方法
2019/11/24 Python
Python 实现网课实时监控自动签到、打卡功能
2020/03/12 Python
通过实例简单了解Python sys.argv[]使用方法
2020/08/04 Python
CSS3中Color的一些特性介绍
2012/05/27 HTML / CSS
荷兰演唱会和体育比赛订票网站:viagogo荷兰
2018/04/08 全球购物
远程学习的教学用品和家庭学习资源:Really Good Stuff
2020/04/27 全球购物
主管会计岗位责任制
2014/02/10 职场文书
党员活动日总结
2014/05/05 职场文书
2014年医院十一国庆节活动方案
2014/09/15 职场文书
团代会邀请函
2015/02/02 职场文书
销售区域经理岗位职责
2015/04/10 职场文书
信用卡工作证明范本
2015/06/19 职场文书
吉利入股戴姆勒后smart“长大了”
2022/04/21 数码科技