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中用于转换字母为小写的lower()方法使用简介
May 19 Python
python函数装饰器用法实例详解
Jun 04 Python
利用python代码写的12306订票代码
Dec 20 Python
Python 类的继承实例详解
Mar 25 Python
python3大文件解压和基本操作
Dec 15 Python
Python实现PS滤镜中马赛克效果示例
Jan 20 Python
Python八大常见排序算法定义、实现及时间消耗效率分析
Apr 27 Python
Anaconda 离线安装 python 包的操作方法
Jun 11 Python
Python使用装饰器模拟用户登陆验证功能示例
Aug 24 Python
Python3.7+tkinter实现查询界面功能
Dec 24 Python
解决pycharm导入本地py文件时,模块下方出现红色波浪线的问题
Jun 01 Python
python 最简单的实现适配器设计模式的示例
Jun 30 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
《星际争霸2》终章已出 RTS时代宣告终结
2017/02/07 星际争霸
PHP的加密方式及原理
2012/06/14 PHP
解决微信授权回调页面域名只能设置一个的问题
2016/12/11 PHP
php实现session共享的实例方法
2019/09/19 PHP
PhpSpreadsheet设置单元格常用操作汇总
2020/11/13 PHP
些很实用且必用的小脚本代码
2006/06/26 Javascript
jqPlot Option配置对象详解
2009/07/25 Javascript
通过jQuery打造支持汉字,拼音,英文快速定位查询的超级select插件
2010/06/18 Javascript
asm.js使用示例代码
2013/11/28 Javascript
node.js中的querystring.unescape方法使用说明
2014/12/10 Javascript
node.js中的fs.fchmodSync方法使用说明
2014/12/16 Javascript
JQuery中的事件及动画用法实例
2015/01/26 Javascript
基于OL2实现百度地图ABCD marker的效果
2015/10/01 Javascript
用window.onerror捕获并上报Js错误的方法
2016/01/27 Javascript
vue自定义移动端touch事件之点击、滑动、长按事件
2018/07/10 Javascript
详解angular部署到iis出现404解决方案
2018/08/14 Javascript
vue引入微信sdk 实现分享朋友圈获取地理位置功能
2019/07/04 Javascript
html2canvas属性和使用方法以及如何使用html2canvas将HTML内容写入Canvas生成图片
2020/01/12 Javascript
[02:28]DOTA2亚洲邀请赛 LGD战队巡礼
2015/02/03 DOTA
Python获取远程文件大小的函数代码分享
2014/05/13 Python
python使用PyGame模块播放声音的方法
2015/05/20 Python
python3.5基于TCP实现文件传输
2020/03/20 Python
在windows下使用python进行串口通讯的方法
2019/07/02 Python
python实现人机猜拳小游戏
2020/02/03 Python
pycharm 2020 1.1的安装流程
2020/09/29 Python
澳大利亚儿童和婴儿产品在线商店:Lime Tree Kids
2017/10/05 全球购物
加拿大折扣、优惠券和交易网站:WagJag
2018/02/07 全球购物
牵手50台湾:专为黄金岁月的单身人士而设的交友网站
2021/02/18 全球购物
Kingsoft金山公司C/C++笔试题
2016/05/10 面试题
业务副厂长岗位职责
2014/01/03 职场文书
教师求职自荐信范文
2015/03/04 职场文书
关于成立领导小组的通知
2015/04/23 职场文书
2015年党风建设工作总结
2015/04/29 职场文书
故意伤害罪辩护词
2015/05/21 职场文书
python文件与路径操作神器 pathlib
2022/04/01 Python
前端使用svg图片改色实现示例
2022/07/23 HTML / CSS