对于Python装饰器使用的一些建议


Posted in Python onJune 03, 2015

装饰器基本概念

大家都知道装饰器是一个很著名的设计模式,经常被用于 AOP (面向切面编程)的场景,较为经典的有插入日志,性能测试,事务处理,Web权限校验, Cache等。

Python 语言本身提供了装饰器语法(@),典型的装饰器实现如下:

@function_wrapper
  def function():
    pass

@实际上是 python2.4 才提出的语法糖,针对 python2.4 以前的版本有另一种等价的实现:

def function():
    pass

  function = function_wrapper(function)

装饰器的两种实现

函数包装器 - 经典实现

def function_wrapper(wrapped):
    def _wrapper(*args, **kwargs):
      return wrapped(*args, **kwargs)
    return _wrapper 

  @function_wrapper
  def function():
    pass

类包装器 - 易于理解

class function_wrapper(object):
    def __init__(self, wrapped):
      self.wrapped = wrapped
    def __call__(self, *args, **kwargs):
      return self.wrapped(*args, **kwargs)

  @function_wrapper
  def function():
    pass

函数(function)自省

当我们谈到一个函数时,通常希望这个函数的属性像其文档上描述的那样,是被明确定义的,例如__name__ 和__doc__ 。

针对某个函数应用装饰器时,这个函数的属性就会发生变化,但这并不是我们所期望的。

def function_wrapper(wrapped):
    def _wrapper(*args, **kwargs):
      return wrapped(*args, **kwargs)
    return _wrapper 

  @function_wrapper
  def function():
    pass 

  >>> print(function.__name__)
  _wrapper

python 标准库提供了functools.wraps(),来解决这个问题。

import functools 

  def function_wrapper(wrapped):
    @functools.wraps(wrapped)
    def _wrapper(*args, **kwargs):
      return wrapped(*args, **kwargs)
    return _wrapper 

  @function_wrapper
  def function():
    pass 

  >>> print(function.__name__)
  function

然而,当我们想要获取被包装函数的参数(argument)或源代码(source code)时,同样不能得到我们想要的结果。

import inspect 

  def function_wrapper(wrapped): ...

  @function_wrapper
  def function(arg1, arg2): pass 

  >>> print(inspect.getargspec(function))
  ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None)

  >>> print(inspect.getsource(function))
    @functools.wraps(wrapped)
    def _wrapper(*args, **kwargs):
      return wrapped(*args, **kwargs)

包装类方法(@classmethod)

当包装器(@function_wrapper)被应用于@classmethod时,将会抛出如下异常:

class Class(object):
    @function_wrapper
    @classmethod
    def cmethod(cls):
      pass 

  Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "<stdin>", line 3, in Class
   File "<stdin>", line 2, in wrapper
   File ".../functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
  AttributeError: 'classmethod' object has no attribute '__module__'

因为@classmethod在实现时,缺少functools.update_wrapper需要的某些属性。这是functools.update_wrapper在 python2 中的 bug,3.2版本已被修复,参考 http://bugs.python.org/issue3445。

然而,在 python3 下执行,另一个问题出现了:

class Class(object):
    @function_wrapper
    @classmethod
    def cmethod(cls):
      pass 

  >>> Class.cmethod() 
  Traceback (most recent call last):
   File "classmethod.py", line 15, in <module>
    Class.cmethod()
   File "classmethod.py", line 6, in _wrapper
    return wrapped(*args, **kwargs)
  TypeError: 'classmethod' object is not callable

这是因为包装器认定被包装的函数(@classmethod )是可以直接被调用的,但事实并不一定是这样的。被包装的函数实际上可能是描述符(descriptor ),意味着为了使其可调用,该函数(描述符)必须被正确地绑定到某个实例上。关于描述符的定义,可以参考 https://docs.python.org/2/howto/descriptor.html。
总结 - 简单并不意味着正确

尽管大家实现装饰器所用的方法通常都很简单,但这并不意味着它们一定是正确的并且始终能正常工作。

如同上面我们所看到的,functools.wraps() 可以帮我们解决__name__ 和__doc__ 的问题,但对于获取函数的参数(argument)或源代码( source code )则束手无策。

Python 相关文章推荐
详解Python之unittest单元测试代码
Jan 24 Python
python如何对实例属性进行类型检查
Mar 20 Python
python二维列表一维列表的互相转换实例
Jul 02 Python
python实现Zabbix-API监控
Sep 17 Python
python爬虫爬取笔趣网小说网站过程图解
Nov 18 Python
python实现将json多行数据传入到mysql中使用
Dec 31 Python
Python实现大数据收集至excel的思路详解
Jan 03 Python
python3实现raspberry pi(树莓派)4驱小车控制程序
Feb 12 Python
Python常见反爬虫机制解决方案
Jun 01 Python
Python3爬虫里关于Splash负载均衡配置详解
Jul 10 Python
如何用用Python将地址标记在地图上
Feb 07 Python
python实现学生信息管理系统源码
Feb 22 Python
Python模块搜索概念介绍及模块安装方法介绍
Jun 03 #Python
Python使用ftplib实现简易FTP客户端的方法
Jun 03 #Python
Python中的深拷贝和浅拷贝详解
Jun 03 #Python
python下paramiko模块实现ssh连接登录Linux服务器
Jun 03 #Python
python处理二进制数据的方法
Jun 03 #Python
Python读写配置文件的方法
Jun 03 #Python
python操作ssh实现服务器日志下载的方法
Jun 03 #Python
You might like
php中的MVC模式运用技巧
2007/05/03 PHP
php join函数应用
2011/05/04 PHP
php实现多张图片上传加水印技巧
2013/04/18 PHP
PHPMailer邮件发送的实现代码
2013/05/04 PHP
PHP zip扩展Linux下安装过程分享
2014/05/05 PHP
WampServer下安装多个版本的PHP、mysql、apache图文教程
2015/01/07 PHP
php字符串函数学习之substr()
2015/03/27 PHP
让焦点自动跳转
2006/07/01 Javascript
使用EXT实现无刷新动态调用股票信息
2008/11/01 Javascript
在jquery boxy中添加百度地图坐标拾取注意流程
2014/04/03 Javascript
JavaScript数值转换的三种方式总结
2014/07/31 Javascript
深入探密Javascript数组方法
2015/01/08 Javascript
JavaScript学习笔记之JS对象
2015/01/22 Javascript
举例说明JavaScript中的实例对象与原型对象
2016/03/11 Javascript
JS解决移动web开发手机输入框弹出的问题
2017/03/31 Javascript
nodejs取得当前执行路径的方法
2018/05/13 NodeJs
Bootstrap-table自定义可编辑每页显示记录数
2018/09/07 Javascript
Node.js JSON模块用法实例分析
2019/01/04 Javascript
微信小程序仿通讯录功能
2020/04/09 Javascript
js中复选框的取值及赋值示例详解
2020/10/18 Javascript
Python FTP操作类代码分享
2014/05/13 Python
Python SqlAlchemy动态添加数据表字段实例解析
2018/02/07 Python
Python之list对应元素求和的方法
2018/06/28 Python
Python使用pickle模块报错EOFError Ran out of input的解决方法
2018/08/16 Python
记一次python 内存泄漏问题及解决过程
2018/11/29 Python
python机器学习实现决策树
2019/11/11 Python
pytorch实现focal loss的两种方式小结
2020/01/02 Python
.NET方向面试题
2014/11/20 面试题
办公文员的工作岗位职责
2013/11/12 职场文书
大学社团活动总结
2014/04/26 职场文书
法语专业求职信
2014/07/20 职场文书
意外伤害赔偿协议书范本
2014/09/28 职场文书
查摆问题自查报告范文
2014/10/13 职场文书
2015暑假社会调查报告
2015/07/13 职场文书
Go语言空白表示符_的实例用法
2021/07/04 Golang
Nginx配置文件详解以及优化建议指南
2021/09/15 Servers