举例讲解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装饰器在Django框架下去除冗余代码的教程
Apr 16 Python
破解安装Pycharm的方法
Oct 19 Python
python 的 scapy库,实现网卡收发包的例子
Jul 23 Python
Python操作Mongodb数据库的方法小结
Sep 10 Python
django框架创建应用操作示例
Sep 26 Python
Python 用turtle实现用正方形画圆的例子
Nov 21 Python
Pyecharts绘制全球流向图的示例代码
Jan 08 Python
从多个tfrecord文件中无限读取文件的例子
Feb 17 Python
Python自动发送和收取邮件的方法
Aug 12 Python
python使用建议技巧分享(三)
Aug 18 Python
python3.8.3安装教程及环境配置的详细教程(64-bit)
Nov 28 Python
Pytorch 中的optimizer使用说明
Mar 03 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数组和explode函数示例总结
2015/05/08 PHP
Yii2.0 RESTful API 基础配置教程详解
2018/12/26 PHP
飞鱼(shqlsl) javascript作品集
2006/12/16 Javascript
jquery属性选择器not has怎么写 行悬停高亮显示
2013/11/13 Javascript
跟我学习javascript的prototype使用注意事项
2015/11/17 Javascript
详解AngularJS中module模块的导入导出
2015/12/10 Javascript
JavaScript String 对象常用方法详解
2016/05/13 Javascript
Bootstrap Metronic完全响应式管理模板学习笔记
2016/07/08 Javascript
深入浅析JavaScript函数前面的加号和叹号
2016/07/09 Javascript
总结在前端排序中遇到的问题
2016/07/19 Javascript
js动态添加的DIV中的onclick事件简单实例
2016/07/25 Javascript
轻松掌握JavaScript状态模式
2016/09/07 Javascript
Bootstrap实现的标签页内容切换显示效果示例
2017/05/25 Javascript
Vue.js实现在下拉列表区域外点击即可关闭下拉列表的功能(自定义下拉列表)
2017/05/30 Javascript
通过学习bootstrop导航条学会修改bootstrop颜色基调
2017/06/11 Javascript
解决Layui 表单提交数据为空的问题
2018/08/15 Javascript
一百行JS代码实现一个校验工具
2019/04/30 Javascript
微信小程序iOS下拉白屏晃动问题解决方案
2019/10/12 Javascript
Vue解析剪切板图片并实现发送功能
2020/02/04 Javascript
[01:34]DAC2018主赛事第四日五佳镜头 Gh巨牙海民助Miracle-死里逃生
2018/04/07 DOTA
使用Python的Django框架结合jQuery实现AJAX购物车页面
2016/04/11 Python
深入理解Python3 内置函数大全
2017/11/23 Python
使用Python搭建虚拟环境的配置方法
2018/02/28 Python
Python缓存技术实现过程详解
2019/09/25 Python
Tensorflow 自定义loss的情况下初始化部分变量方式
2020/01/06 Python
Python线程条件变量Condition原理解析
2020/01/20 Python
python 给图像添加透明度(alpha通道)
2020/04/09 Python
泰坦健身器材:Titan Fitness
2018/02/13 全球购物
初中女生自我鉴定
2013/12/19 职场文书
物业保安主管岗位职责
2013/12/25 职场文书
汉语言文学职业规划
2014/02/14 职场文书
国际贸易毕业生求职信范文
2014/02/21 职场文书
经典公益广告词
2014/03/13 职场文书
土地转让协议书范本
2014/04/15 职场文书
竞选宣传委员演讲稿
2014/05/24 职场文书
Python爬虫入门案例之爬取二手房源数据
2021/10/16 Python