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中numpy包使用教程之数组和相关操作详解
Jul 30 Python
如何利用python查找电脑文件
Apr 27 Python
PyTorch快速搭建神经网络及其保存提取方法详解
Apr 28 Python
Python中几种属性访问的区别与用法详解
Oct 10 Python
简单了解python单例模式的几种写法
Jul 01 Python
Python定时发送天气预报邮件代码实例
Sep 09 Python
python 实现矩阵按对角线打印
Nov 29 Python
python实现两个字典合并,两个list合并
Dec 02 Python
Pytorch 的损失函数Loss function使用详解
Jan 02 Python
python使用openCV遍历文件夹里所有视频文件并保存成图片
Jan 14 Python
Pycharm远程连接服务器并实现代码同步上传更新功能
Feb 25 Python
python中rb含义理解
Jun 18 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 截取字符串函数整理(支持gb2312和utf-8)
2010/02/16 PHP
PHP版国家代码、缩写查询函数代码
2011/08/14 PHP
php操作mysql数据库的基本类代码
2014/02/25 PHP
php实现中文字符截取防乱码方法汇总
2015/04/29 PHP
高质量PHP代码的50个实用技巧必备(下)
2016/01/22 PHP
PHP实现分布式memcache设置web集群session同步的方法
2018/04/10 PHP
JavaScript中判断对象类型的几种方法总结
2013/11/11 Javascript
JavaScript中eval函数的问题
2016/01/31 Javascript
使用jQuery.form.js/springmvc框架实现文件上传功能
2016/05/12 Javascript
JS iFrame加载慢怎么解决
2016/05/13 Javascript
利用JQuery实现datatables插件的增加和删除行功能
2017/01/06 Javascript
JavaScript 函数节流详解及方法总结
2017/02/09 Javascript
jQuery遍历节点方法汇总(推荐)
2017/05/13 jQuery
Vue-路由导航菜单栏的高亮设置方法
2018/03/17 Javascript
如何在微信小程序中实现Mixins方案
2019/06/20 Javascript
js实现上传图片并显示图片名称
2019/12/18 Javascript
js实现的订阅发布者模式简单示例
2020/03/14 Javascript
VUE页面中通过双击实现复制表格中内容的示例代码
2020/06/11 Javascript
[01:08:30]DOTA2-DPC中国联赛 正赛 Ehome vs Elephant BO3 第一场 2月28日
2021/03/11 DOTA
python远程登录代码
2008/04/29 Python
在Django中创建动态视图的教程
2015/07/15 Python
12步入门Python中的decorator装饰器使用方法
2016/06/20 Python
对比Python中__getattr__和 __getattribute__获取属性的用法
2016/06/21 Python
使用pandas对两个dataframe进行join的实例
2018/06/08 Python
python pycharm最新版本激活码(永久有效)附python安装教程
2020/09/18 Python
Keras中的多分类损失函数用法categorical_crossentropy
2020/06/11 Python
Python中Selenium库使用教程详解
2020/07/23 Python
Html5实现移动端、PC端 刮刮卡效果
2016/06/30 HTML / CSS
详解基于canvas的视频遮罩插件
2018/01/04 HTML / CSS
Sneaker Studio法国:购买运动鞋
2018/06/08 全球购物
教师自荐书
2013/10/08 职场文书
人力资源部经理岗位职责规定
2014/02/23 职场文书
2015年社区党建工作汇报材料
2015/06/25 职场文书
大学学习委员竞选稿
2015/11/20 职场文书
导游词之青城山景区
2019/09/27 职场文书
JavaScript实现淘宝商品图切换效果
2021/04/29 Javascript