举例讲解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中使用 Selenium 实现网页截图实例
Jul 18 Python
Django查找网站项目根目录和对正则表达式的支持
Jul 15 Python
Python实现TCP/IP协议下的端口转发及重定向示例
Jun 14 Python
Python使用requests发送POST请求实例代码
Jan 25 Python
Python实现批量读取图片并存入mongodb数据库的方法示例
Apr 02 Python
python3使用smtplib实现发送邮件功能
May 22 Python
python打印9宫格、25宫格等奇数格 满足横竖斜相加和相等
Jul 19 Python
wxPython修改文本框颜色过程解析
Feb 14 Python
python GUI库图形界面开发之PyQt5信号与槽事件处理机制详细介绍与实例解析
Mar 08 Python
win10从零安装配置pytorch全过程图文详解
May 08 Python
详解numpy.ndarray.reshape()函数的参数问题
Oct 13 Python
详解非极大值抑制算法之Python实现
Jun 28 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/03 咖啡文化
php获取json数据所有的节点路径
2015/05/17 PHP
详解PHP的Yii框架的运行机制及其路由功能
2016/03/17 PHP
PHP依赖注入原理与用法分析
2018/08/21 PHP
javascript eval函数深入认识
2009/02/21 Javascript
javascript让setInteval里的函数参数中的this指向特定的对象
2010/01/31 Javascript
JS实现鼠标滑过链接改变网页背景颜色的方法
2015/10/20 Javascript
AngularJS 表达式详解及实例代码
2016/09/14 Javascript
深入理解Node.js的HTTP模块
2016/10/12 Javascript
微信小程序 教程之条件渲染
2016/10/18 Javascript
详解vue事件对象、冒泡、阻止默认行为
2017/03/20 Javascript
使用vue框架 Ajax获取数据列表并用BootStrap显示出来
2017/04/24 Javascript
jQuery.form.js的使用详解
2017/06/14 jQuery
vue2.x 父组件监听子组件事件并传回信息的方法
2017/07/17 Javascript
使用vue-router完成简单导航功能【推荐】
2018/06/28 Javascript
解决angularjs前后端分离调用接口传递中文时中文乱码的问题
2018/08/13 Javascript
Node.js 多进程处理CPU密集任务的实现
2019/05/26 Javascript
vue中input的v-model清空操作
2019/09/06 Javascript
使用layui日期控件laydate对开始和结束时间进行联动控制的方法
2019/09/06 Javascript
[00:50]深扒TI7聊天轮盘语音出处6
2017/05/11 DOTA
[08:40]Navi Vs Newbee
2018/06/07 DOTA
用Python的urllib库提交WEB表单
2009/02/24 Python
用Python实现一个简单的能够上传下载的HTTP服务器
2015/05/05 Python
Python获取邮件地址的方法
2015/07/10 Python
Pandas库之DataFrame使用的学习笔记
2019/06/21 Python
python 中如何获取列表的索引
2019/07/02 Python
python list转置和前后反转的例子
2019/08/26 Python
Python拆分大型CSV文件代码实例
2019/10/07 Python
Python SELENIUM上传文件或图片实现过程
2019/10/28 Python
使用matplotlib绘制图例标签中带有公式的图
2019/12/13 Python
python学生信息管理系统实现代码
2019/12/17 Python
python 绘制场景热力图的示例
2020/09/23 Python
绿色环保标语
2014/06/12 职场文书
打架赔偿协议书范本
2014/10/26 职场文书
2014年企业团支部工作总结
2014/12/10 职场文书
创业计划书之情侣餐厅
2019/09/29 职场文书