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基本数据类型详细介绍
Mar 11 Python
Python制作爬虫抓取美女图
Jan 20 Python
Python实现通讯录功能
Feb 22 Python
通过Python 接口使用OpenCV的方法
Apr 02 Python
python2与python3共存问题的解决方法
Sep 18 Python
对pyqt5中QTabWidget的相关操作详解
Jun 21 Python
Python FFT合成波形的实例
Dec 04 Python
基于keras输出中间层结果的2种实现方式
Jan 24 Python
解决python -m pip install --upgrade pip 升级不成功问题
Mar 05 Python
Python3实现建造者模式的示例代码
Jun 28 Python
如何利用python发送邮件
Sep 26 Python
如何用Python编写一个电子考勤系统
Feb 08 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中使用Select 查询语句的实例
2014/02/19 PHP
php计算数组相同值出现次数的代码(array_count_values)
2015/01/20 PHP
php+ajax实现无刷新分页
2015/11/18 PHP
Symfony2学习笔记之控制器用法详解
2016/03/17 PHP
jQuery EasyUI API 中文文档 - Parser 解析器
2011/09/29 Javascript
Javascript图像处理思路及实现代码
2012/12/25 Javascript
EXTjs4.0的store的findRecord的BUG演示代码
2013/06/08 Javascript
简单时间提示DEMO从0开始一直进行计时
2013/11/19 Javascript
Javascript闭包(Closure)详解
2015/05/05 Javascript
jquery插件jquery.nicescroll实现图片无滚动条左右拖拽的方法
2015/08/10 Javascript
浅谈javascript中的constructor
2016/06/08 Javascript
AngularJS基础 ng-src 指令简单示例
2016/08/03 Javascript
Jquery Easyui分割按钮组件SplitButton使用详解(17)
2016/12/18 Javascript
JavaScript函数中的this四种绑定形式
2017/08/15 Javascript
node版本管理工具n包使用教程详解
2018/11/09 Javascript
Layui给switch添加响应事件的例子
2019/09/03 Javascript
javascript中的相等操作符(==与===区别)
2019/12/21 Javascript
Vue Cli3 打包配置并自动忽略console.log语句的方法
2020/04/23 Javascript
vue $mount 和 el的区别说明
2020/09/11 Javascript
python实现微信接口(itchat)详细介绍
2017/10/23 Python
详解K-means算法在Python中的实现
2017/12/05 Python
python pandas 对series和dataframe的重置索引reindex方法
2018/06/07 Python
python中dir()与__dict__属性的区别浅析
2018/12/10 Python
Python的PIL库中getpixel方法的使用
2020/04/09 Python
Python中zipfile压缩文件模块的基本使用教程
2020/06/14 Python
详解Python 函数参数的拆解
2020/09/02 Python
python+selenium自动化实战携带cookies模拟登陆微博
2021/01/19 Python
浅析数据存储的三种方式 cookie sessionstorage localstorage 的异同
2020/06/04 HTML / CSS
EJB3.1都有哪些改进
2012/11/17 面试题
学前教育毕业生自荐信范文
2013/12/24 职场文书
受欢迎的自荐信,就这么写!
2019/04/19 职场文书
apache基于端口创建虚拟主机的示例
2021/04/22 Servers
react国际化react-intl的使用
2021/05/06 Javascript
微信小程序实现拍照和相册选取图片
2021/05/09 Javascript
Pytorch实现图像识别之数字识别(附详细注释)
2021/05/11 Python
Python学习之异常中的finally使用详解
2022/03/16 Python