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每次处理固定个数的字符的方法总结
Jan 29 Python
Python中使用glob和rmtree删除目录子目录及所有文件的例子
Nov 21 Python
python二分查找算法的递归实现方法
May 12 Python
Python使用SocketServer模块编写基本服务器程序的教程
Jul 12 Python
Python数据结构与算法之字典树实现方法示例
Dec 13 Python
Python中str.join()简单用法示例
Mar 20 Python
多个应用共存的Django配置方法
May 30 Python
python3实现点餐系统
Jan 24 Python
django admin.py 外键,反向查询的实例
Jul 26 Python
Python自定义聚合函数merge与transform区别详解
May 26 Python
用python-webdriver实现自动填表的示例代码
Jan 13 Python
python+opencv3.4.0 实现HOG+SVM行人检测的示例代码
Jan 28 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
日本十大惊悚动漫
2020/03/04 日漫
检查用户名是否已在mysql中存在的php写法
2014/01/20 PHP
PHP实现的观察者模式实例
2017/06/21 PHP
浅谈PHP匿名函数和闭包
2019/03/08 PHP
JavaScript 事件对象的实现
2009/07/13 Javascript
extjs 学习笔记(二) Ext.Element类
2009/10/13 Javascript
麻雀虽小五脏俱全 Dojo自定义控件应用
2010/09/04 Javascript
JS计算网页停留时间代码
2014/04/28 Javascript
深入分析JQuery和JavaScript的异同
2014/10/23 Javascript
node.js中的buffer.slice方法使用说明
2014/12/10 Javascript
JavaScript的内存释放问题详解
2015/01/21 Javascript
js实现拉幕效果的广告代码
2015/09/02 Javascript
实例解析jQuery中proxy()函数的用法
2016/05/24 Javascript
在线引用最新jquery文件的实现方法
2016/08/26 Javascript
js实现文字超出部分用省略号代替实例代码
2016/09/01 Javascript
Bootstrap图片轮播组件Carousel使用方法详解
2016/10/20 Javascript
Angularjs使用ng-repeat中$even和$odd属性的注意事项
2016/12/31 Javascript
vue.js中指令Directives详解
2017/03/20 Javascript
jQuery实现在HTML文档加载完毕后自动执行某个事件的方法
2017/05/08 jQuery
webpack打包单页面如何引用的js
2017/06/07 Javascript
jquery+css3实现熊猫tv导航代码分享
2018/02/12 jQuery
前端axios下载excel文件(二进制)的处理方法
2018/07/31 Javascript
Vue Router去掉url中默认的锚点#
2018/08/01 Javascript
Python单例模式的两种实现方法
2017/08/14 Python
Python中使用支持向量机SVM实践
2017/12/27 Python
对PyQt5的输入对话框使用(QInputDialog)详解
2019/06/25 Python
手机使用python操作图片文件(pydroid3)过程详解
2019/09/25 Python
Python函数递归调用实现原理实例解析
2020/08/11 Python
HTML5跳转小程序wx-open-launch-weapp的示例代码
2020/07/16 HTML / CSS
意大利折扣和优惠券网站:Groupalia
2019/10/09 全球购物
简述进程的启动、终止的方式以及如何进行进程的查看
2013/07/12 面试题
采购部岗位职责
2013/11/24 职场文书
学生会干部自荐信
2014/02/04 职场文书
中学教师教育感言
2014/02/21 职场文书
志愿者活动总结
2014/04/28 职场文书
乡镇干部个人对照检查材料思想汇报
2014/10/04 职场文书