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 相关文章推荐
使用wxPython获取系统剪贴板中的数据的教程
May 06 Python
Python中enumerate函数代码解析
Oct 31 Python
Pandas 对Dataframe结构排序的实现方法
Apr 10 Python
pandas series序列转化为星期几的实例
Apr 11 Python
实例介绍Python中整型
Feb 11 Python
python仿抖音表白神器
Apr 08 Python
使用python telnetlib批量备份交换机配置的方法
Jul 25 Python
详解Python3定时器任务代码
Sep 23 Python
使用Python测试Ping主机IP和某端口是否开放的实例
Dec 17 Python
python使用openCV遍历文件夹里所有视频文件并保存成图片
Jan 14 Python
Python在字符串中处理html和xml的方法
Jul 31 Python
Python实现生成bmp图像的方法
Jun 13 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中10个不常见却非常有用的函数
2010/03/21 PHP
PHP similar_text 字符串的相似性比较函数
2010/05/26 PHP
由php中字符offset特征造成的绕过漏洞详解
2017/07/07 PHP
实例分析PHP中PHPMailer发邮件
2017/12/13 PHP
jQuery 创建Dom元素
2010/05/07 Javascript
jQuery-ui中自动完成实现方法
2010/06/10 Javascript
8款非常棒的响应式jQuery 幻灯片插件推荐
2012/02/02 Javascript
ListBox实现上移,下移,左移,右移的简单实例
2014/02/13 Javascript
js实现弹出窗口、页面变成灰色并不可操作的例子分享
2014/05/10 Javascript
JavaScript如何调试有哪些建议和技巧附五款有用的调试工具
2015/10/28 Javascript
学JavaScript七大注意事项【必看】
2016/05/04 Javascript
jQuery控制li上下循环滚动插件用法实例(附demo源码下载)
2016/05/28 Javascript
浅谈$('div a') 与$('div&gt;a')的区别
2016/07/18 Javascript
javascript滚轮控制模拟滚动条
2016/10/19 Javascript
使用get方式提交表单在地址栏里面不显示提交信息
2017/02/21 Javascript
JavaScrpt的面向对象全面解析
2017/05/09 Javascript
vue如何使用 Slot 分发内容实例详解
2017/09/05 Javascript
vue-cli开发时,关于ajax跨域的解决方法(推荐)
2018/02/03 Javascript
JavaScript图片处理与合成总结
2018/03/04 Javascript
vue.js内置组件之keep-alive组件使用
2018/07/10 Javascript
微信小程序文字显示换行问题
2019/07/28 Javascript
vue中使用elementUI组件手动上传图片功能
2019/12/13 Javascript
vue+AI智能机器人回复功能实现
2020/07/16 Javascript
Python实现PS滤镜碎片特效功能示例
2018/01/24 Python
详解用TensorFlow实现逻辑回归算法
2018/05/02 Python
Django+Ajax+jQuery实现网页动态更新的实例
2018/05/28 Python
联想C++笔试题
2012/06/13 面试题
Laravel的加密解密与哈希实例讲解
2021/03/24 PHP
宿舍违规用电检讨书
2014/02/16 职场文书
优乐美广告词
2014/03/14 职场文书
GMP办公室主任岗位职责
2014/03/14 职场文书
诚信考试倡议书
2014/04/15 职场文书
如何写股份合作协议书
2014/09/11 职场文书
单方离婚协议书范本2014
2014/10/28 职场文书
幼儿教师2014年度工作总结
2014/12/16 职场文书
分析SQL窗口函数之取值窗口函数
2022/04/21 Oracle