Python中使用装饰器时需要注意的一些问题


Posted in Python onMay 11, 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)则束手无策。

以上问题,wrapt都可以帮忙解决,详细用法可参考其官方文档:http://wrapt.readthedocs.org

Python 相关文章推荐
python 示例分享---逻辑推理编程解决八皇后
Jul 20 Python
Python通过解析网页实现看报程序的方法
Aug 04 Python
利用python代码写的12306订票代码
Dec 20 Python
python实现的正则表达式功能入门教程【经典】
Jun 05 Python
Python使用matplotlib绘制多个图形单独显示的方法示例
Mar 14 Python
Python打开文件,将list、numpy数组内容写入txt文件中的方法
Oct 26 Python
Python推导式简单示例【列表推导式、字典推导式与集合推导式】
Dec 04 Python
python3.4+pycharm 环境安装及使用方法
Jun 13 Python
python3 线性回归验证方法
Jul 09 Python
通过字符串导入 Python 模块的方法详解
Oct 27 Python
Django实现分页显示效果
Oct 31 Python
Python机器学习之逻辑回归
May 11 Python
python在linux系统下获取系统内存使用情况的方法
May 11 #Python
Python实现登录人人网并抓取新鲜事的方法
May 11 #Python
python实现中文输出的两种方法
May 09 #Python
python使用xlrd实现检索excel中某列含有指定字符串记录的方法
May 09 #Python
Python遍历指定文件及文件夹的方法
May 09 #Python
Python使用chardet判断字符编码
May 09 #Python
python操作ie登陆土豆网的方法
May 09 #Python
You might like
PHP的autoload机制的实现解析
2012/09/15 PHP
php堆排序(heapsort)练习
2013/11/13 PHP
php合并数组中相同元素的方法
2014/11/13 PHP
windows下配置php5.5开发环境及开发扩展
2014/12/25 PHP
浅谈Coreseek、Sphinx-for-chinaese、Sphinx+Scws的区别
2016/12/15 PHP
javascript 面向对象编程基础 多态
2009/08/21 Javascript
jQuery代码优化之基本事件
2011/11/01 Javascript
根据邮箱的域名跳转到相应的登录页面的代码
2012/02/27 Javascript
jquery formValidator插件ajax验证 内容不做任何修改再离开提示错误的bug解决方法
2013/01/04 Javascript
node.js使用npm 安装插件时提示install Error: ENOENT报错的解决方法
2014/11/20 Javascript
js添加绑定事件的方法
2016/05/15 Javascript
jQuery 选择同时包含两个class的元素的实现方法
2016/06/01 Javascript
全面解析Bootstrap中scrollspy(滚动监听)的使用方法
2016/06/06 Javascript
AngularJs Modules详解及示例代码
2016/09/01 Javascript
如何处理JSON中的特殊字符
2016/11/30 Javascript
javascript中活灵活现的Array对象详解
2016/11/30 Javascript
Vue2实现组件props双向绑定
2016/12/02 Javascript
canvas实现图像放大镜
2017/02/06 Javascript
nodejs个人博客开发第五步 分配数据
2017/04/12 NodeJs
vue.js移动端tab组件的封装实践实例
2017/06/30 Javascript
node.js基于fs模块对系统文件及目录进行读写操作的方法详解
2017/11/10 Javascript
vue实现点击隐藏与显示实例分享
2019/02/13 Javascript
JS实现点击生成UUID的方法完整实例【基于jQuery】
2019/06/12 jQuery
Vue使用富文本编辑器Vue-Quill-Editor(含图片自定义上传服务、清除复制粘贴样式等)
2020/05/15 Javascript
[01:12:27]EG vs Secret 2018国际邀请赛淘汰赛BO3 第二场 8.22
2018/08/23 DOTA
Python中的推导式使用详解
2015/06/03 Python
python 求1-100之间的奇数或者偶数之和的实例
2019/06/11 Python
Python爬取数据并实现可视化代码解析
2020/08/12 Python
python批量检查两个对应的txt文件的行数是否一致的实例代码
2020/10/31 Python
Hello Molly美国:女性时尚在线
2019/08/26 全球购物
旅行社各个岗位职责
2014/03/15 职场文书
药剂专业自荐书
2014/06/20 职场文书
励志演讲稿500字
2014/08/21 职场文书
幼儿园六一儿童节活动方案
2014/08/26 职场文书
美德少年主要事迹材料
2015/11/04 职场文书
HAM-2000摩机图
2021/04/22 无线电