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实现巡检系统(solaris)示例
Apr 02 Python
python文件读写操作与linux shell变量命令交互执行的方法
Jan 14 Python
python安装numpy&amp;安装matplotlib&amp; scipy的教程
Nov 02 Python
浅谈Django学习migrate和makemigrations的差别
Jan 18 Python
python批量修改文件编码格式的方法
May 31 Python
python 读取文件并替换字段的实例
Jul 12 Python
Python双向循环链表实现方法分析
Jul 30 Python
解决Pycharm调用Turtle时 窗口一闪而过的问题
Feb 16 Python
用Python配平化学方程式的方法
Jul 20 Python
python爬虫 正则表达式解析
Sep 28 Python
python基于openpyxl生成excel文件
Dec 23 Python
如何解决.cuda()加载用时很长的问题
May 24 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发电子邮件
2006/10/09 PHP
我的论坛源代码(十)
2006/10/09 PHP
PHP类的使用 实例代码讲解
2009/12/28 PHP
将一维或多维的数组连接成一个字符串的php代码
2010/08/08 PHP
PHP使用json_encode函数时不转义中文的解决方法
2014/11/12 PHP
JavaScript接口实现代码 (Interfaces In JavaScript)
2010/06/11 Javascript
file控件选择上传文件确定后触发的js事件是哪个
2014/03/17 Javascript
JavaScript中对象介绍
2014/12/31 Javascript
jQuery弹出窗口打开链接的实现代码
2016/12/24 Javascript
JS中原始值和引用值的储存方式示例详解
2018/03/23 Javascript
Element Table的row-class-name无效与动态高亮显示选中行背景色
2018/11/30 Javascript
Vue实现本地购物车功能
2018/12/05 Javascript
NestJs 静态目录配置详解
2019/03/12 Javascript
如何使用七牛Python SDK写一个同步脚本及使用教程
2015/08/23 Python
python操作字典类型的常用方法(推荐)
2016/05/16 Python
Python读取properties配置文件操作示例
2018/03/29 Python
在Mac上删除自己安装的Python方法
2018/10/29 Python
Python、 Pycharm、Django安装详细教程(图文)
2019/04/12 Python
python实现ip地址的包含关系判断
2020/02/07 Python
pandas 数据类型转换的实现
2020/12/29 Python
英国最大的运动营养公司之一:LA Muscle
2018/07/02 全球购物
eBay美国官网:eBay.com
2020/10/24 全球购物
美国购物网站:Clickhere2shop
2021/01/28 全球购物
ajax是什么及其工作原理
2012/02/08 面试题
经典优秀个人求职自荐信格式
2013/09/25 职场文书
大二法英学生职业生涯规划范文
2014/02/27 职场文书
《鲁班和橹板》教学反思
2014/04/27 职场文书
弘扬雷锋精神演讲稿
2014/05/10 职场文书
教师党的群众路线学习心得体会
2014/11/04 职场文书
会计出纳岗位职责
2015/03/31 职场文书
学校运动会通讯稿
2015/07/18 职场文书
宾馆卫生管理制度
2015/08/06 职场文书
2016党性教育学习心得体会
2016/01/21 职场文书
成功的商业计划书这样写才最靠谱
2019/07/12 职场文书
简单实现一个手持弹幕功能+文字抖动特效
2021/03/31 HTML / CSS
VUE递归树形实现多级列表
2022/07/15 Vue.js