Python 装饰器使用详解


Posted in Python onJuly 29, 2017

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象.

经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

先来看一个简单例子:

def now():
  print('2017_7_29')

现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:

def now():
  print('2017_7_29')
  logging.warn("running")

假设有类似的多个需求,怎么做?再写一个logging在now函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码.

def use_logging(func):   
  logging.warn("%s is running" % func.__name__)   
  func() 

def now():   
  print('2017_7_29') 
  
use_logging(now)

在实现,逻辑上不难, 但是这样的话,我们每次都要将一个函数作为参数传递给日志函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行now(),但是现在不得不改成use_logging(now)。那么有没有更好的方式的呢?当然有,答案就是装饰器。

首先要明白函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。例如:

def now():
  print('2017_7_28')

f=now
f()
# 函数对象有一个__name__属性,可以拿到函数的名字
print('now.__name__:',now.__name__)
print('f.__name__:',f.__name__)

简单装饰器

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

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

# 由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,
# 只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
# wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。
# 在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数.现在执行:

now = log(now)
now()

输出结果:
    call now():
    2017_7_28

函数log就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像now被log装饰了。在这个例子中,函数进入时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。

使用语法糖:

@log
def now():
  print('2017_7_28')

@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作

这样我们就可以省去now = log(now)这一句了,直接调用now()即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。

装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

带参数的装饰器:

如果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('goal')
def now():
  print('2017-7-28')
now()

等价于

now = log('goal')(now)

# 首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数
now()

因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper':

print(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
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

类装饰器:

再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法

import time

class Foo(object):   
  def __init__(self, func):   
    self._func = func 
  
  def __call__(self):   
    print ('class decorator runing')   
    self._func()   
    print ('class decorator ending') 

@Foo 
def now():   
  print (time.strftime('%Y-%m-%d',time.localtime(time.time()))) 
  
now()

总结:

概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

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

Python 相关文章推荐
Python实现根据IP地址和子网掩码算出网段的方法
Jul 30 Python
Python自动化运维和部署项目工具Fabric使用实例
Sep 18 Python
对python 操作solr索引数据的实例详解
Dec 07 Python
Python根据当前日期取去年同星期日期
Apr 14 Python
Django组件cookie与session的具体使用
Jun 05 Python
pytorch中的embedding词向量的使用方法
Aug 18 Python
3行Python代码实现图像照片抠图和换底色的方法
Oct 10 Python
解决pandas展示数据输出时列名不能对齐的问题
Nov 18 Python
如何理解python中数字列表
May 29 Python
新手学python应该下哪个版本
Jun 11 Python
python 调用js的四种方式
Apr 11 Python
使用Python解决图表与画布的间距问题
Apr 11 Python
python实现数据图表
Jul 29 #Python
基于Python的XSS测试工具XSStrike使用方法
Jul 29 #Python
使用Kivy将python程序打包为apk文件
Jul 29 #Python
python对配置文件.ini进行增删改查操作的方法示例
Jul 28 #Python
Python3中使用PyMongo的方法详解
Jul 28 #Python
Python tkinter模块弹出窗口及传值回到主窗口操作详解
Jul 28 #Python
Python单体模式的几种常见实现方法详解
Jul 28 #Python
You might like
深入了解PHP类Class的概念
2012/06/14 PHP
解析关于java,php以及html的所有文件编码与乱码的处理方法汇总
2013/06/24 PHP
php 使用html5实现多文件上传实例
2016/10/24 PHP
PHP函数rtrim()使用中的怪异现象分析
2017/02/24 PHP
PHP设计模式之装饰器模式定义与用法详解
2018/04/02 PHP
实例:用 JavaScript 来操作字符串(一些字符串函数)
2007/02/15 Javascript
Packer 3.0 JS压缩及混淆工具 下载
2007/05/03 Javascript
Whatever:hover 无需javascript让IE支持丰富伪类
2010/06/29 Javascript
jquery 查找新建元素代码
2010/07/06 Javascript
js下获得客户端操作系统的函数代码(1:vista,2:windows7,3:2000,4:xp,5:2003,6:2008)
2011/10/31 Javascript
深入理解JavaScript系列(8) S.O.L.I.D五大原则之里氏替换原则LSP
2012/01/15 Javascript
jquery实现漂浮在网页右侧的qq在线客服插件示例
2013/05/13 Javascript
js 时间函数应用加、减、比较、格式转换的示例代码
2013/08/23 Javascript
js自动生成的元素与页面原有元素发生堆叠的解决方法
2014/09/04 Javascript
node.js中使用socket.io制作命名空间
2014/12/15 Javascript
JavaScript实现将xml转换成html table表格的方法
2015/04/17 Javascript
jquery+ajax实现注册实时验证实例详解
2015/12/08 Javascript
EasyUI闪屏EasyUI页面加载提示(原理+代码+效果图)
2016/02/21 Javascript
一分钟理解js闭包
2016/05/04 Javascript
js将json格式的对象拼接成复杂的url参数方法
2016/05/25 Javascript
JavaScript实现form表单的多文件上传
2020/03/27 Javascript
Angular.js初始化之ng-app的自动绑定与手动绑定详解
2017/07/31 Javascript
将jquery.qqFace.js表情转换成微信的字符码
2017/12/01 jQuery
vue如何截取字符串
2019/05/06 Javascript
Django实现的自定义访问日志模块示例
2017/06/23 Python
Python实现可设置持续运行时间、线程数及时间间隔的多线程异步post请求功能
2018/01/11 Python
python dataframe向下向上填充,fillna和ffill的方法
2018/11/28 Python
python打印9宫格、25宫格等奇数格 满足横竖斜相加和相等
2019/07/19 Python
如何在mac环境中用python处理protobuf
2019/12/25 Python
PyInstaller的安装和使用的详细步骤
2020/06/02 Python
python 抓取知乎指定回答下视频的方法
2020/07/09 Python
anaconda升级sklearn版本的实现方法
2021/02/22 Python
应届毕业生个人自荐信范文
2013/11/30 职场文书
总裁秘书岗位职责
2013/12/04 职场文书
护士自我鉴定怎么写
2014/02/07 职场文书
2014年调度员工作总结
2014/11/19 职场文书