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笔记(2)
Oct 24 Python
Python利用matplotlib生成图片背景及图例透明的效果
Apr 27 Python
Python基于list的append和pop方法实现堆栈与队列功能示例
Jul 24 Python
Python批量发送post请求的实现代码
May 05 Python
对Python中9种生成新对象的方法总结
May 23 Python
Python文件读写保存操作的示例代码
Sep 14 Python
Python 新建文件夹与复制文件夹内所有内容的方法
Oct 27 Python
Python设计模式之简单工厂模式实例详解
Jan 22 Python
python实现视频分帧效果
May 31 Python
Python基于Socket实现简单聊天室
Feb 17 Python
深入了解Python 方法之类方法 &amp; 静态方法
Aug 17 Python
Flask-SocketIO服务端安装及使用代码示例
Nov 26 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
与空气斗智斗勇的经典《Overlord》,传说中的“无稽之谈”
2020/04/09 日漫
php中通过Ajax如何实现异步文件上传的代码实例
2011/05/07 PHP
javascript 一个自定义长度的文本自动换行的函数
2007/08/19 Javascript
jquery select下拉框操作的一些说明
2010/04/02 Javascript
JS操作Cookies包括(读取添加与删除)
2012/12/26 Javascript
jQuery$命名冲突怎么办如何解决
2014/01/16 Javascript
javascript版的in_array函数(判断数组中是否存在特定值)
2014/05/09 Javascript
2014 年最热门的21款JavaScript框架推荐
2014/12/25 Javascript
js只执行1次的函数示例
2016/07/20 Javascript
微信小程序第三方框架对比 之 wepy / mpvue / taro
2019/04/10 Javascript
JS开发常用工具函数(小结)
2019/07/04 Javascript
关于layui表单中按钮自动提交的解决方法
2019/09/09 Javascript
Openlayers实现地图的基本操作
2020/09/28 Javascript
[40:05]LGD vs Winstrike 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/18 DOTA
python基础教程之udp端口扫描
2014/02/10 Python
Python实现的逻辑回归算法示例【附测试csv文件下载】
2018/12/28 Python
Python设计模式之工厂方法模式实例详解
2019/01/18 Python
Python高阶函数、常用内置函数用法实例分析
2019/12/26 Python
Python虚拟环境virtualenv创建及使用过程图解
2020/12/08 Python
使用canvas绘制超炫时钟
2014/12/17 HTML / CSS
日本最佳原创设计品牌:Felissimo(芬理希梦)
2019/03/19 全球购物
业务副厂长岗位职责
2014/01/03 职场文书
新书吧创业计划书
2014/01/31 职场文书
领导党性分析材料
2014/02/15 职场文书
《孔子拜师》教学反思
2014/02/24 职场文书
物业经理自我鉴定
2014/03/03 职场文书
迎新晚会主持词
2014/03/24 职场文书
文体活动实施方案
2014/03/27 职场文书
乡镇党员干部四风对照检查材料思想汇报
2014/09/27 职场文书
2015年初中元旦晚会活动总结
2014/11/28 职场文书
奖学金发言稿(范文)
2019/08/21 职场文书
基于tensorflow权重文件的解读
2021/05/26 Python
原生JavaScript实现简单五子棋游戏
2021/06/28 Javascript
一次MySQL启动导致的事故实战记录
2021/09/15 MySQL
Django实现WebSocket在线聊天室功能(channels库)
2021/09/25 Python
使用Python获取字典键对应值的方法
2022/04/26 Python