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 相关文章推荐
Cython 三分钟入门教程
Sep 17 Python
Python subprocess模块学习总结
Mar 13 Python
Python HTTP客户端自定义Cookie实现实例
Apr 28 Python
Python在不同目录下导入模块的实现方法
Oct 27 Python
python执行系统命令后获取返回值的几种方式集合
May 12 Python
详解Python函数式编程—高阶函数
Mar 29 Python
python自制包并用pip免提交到pypi仅安装到本机【推荐】
Jun 03 Python
Python学习笔记之函数的参数和返回值的使用
Nov 20 Python
Python 3.8 新功能来一波(大部分人都不知道)
Mar 11 Python
浅谈tensorflow模型保存为pb的各种姿势
May 25 Python
python输入中文的实例方法
Sep 14 Python
python 利用Pyinstaller打包Web项目
Oct 23 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
世界上第一台立体声收音机
2021/03/01 无线电
基于文本的访客签到簿
2006/10/09 PHP
Uchome1.2 1.5 代码学习 common.php
2009/04/24 PHP
简单的PHP留言本实例代码
2010/05/09 PHP
php注销代码(session注销)
2012/05/31 PHP
php常用Output和ptions/Info函数集介绍
2013/06/19 PHP
PHP实现基于PDO扩展连接PostgreSQL对象关系数据库示例
2018/03/31 PHP
Bootstrap+PHP实现多图上传功能实例详解
2018/04/08 PHP
javascript 获取表单file全路径
2009/12/31 Javascript
JavaScript中继承的一些示例方法与属性参考
2010/08/07 Javascript
js返回前一页刷新本页重载页面
2014/07/29 Javascript
JavaScript中实现异步编程模式的4种方法
2014/09/24 Javascript
JavaScript控制listbox列表框的项目上下移动的方法
2015/03/18 Javascript
js添加事件的通用方法推荐
2016/05/15 Javascript
JS实现的驼峰式和连字符式转换功能分析
2016/12/21 Javascript
微信小程序 摇一摇抽奖简单实例实现代码
2017/01/09 Javascript
jquery表单验证插件validation使用方法详解
2017/01/20 Javascript
vue表单绑定实现多选框和下拉列表的实例
2017/08/12 Javascript
完美解决mui框架off-canvas侧滑超出部分隐藏无法滚动的问题
2018/01/25 Javascript
Vue自动构建发布脚本的方法示例
2020/07/24 Javascript
python常见排序算法基础教程
2017/04/13 Python
详解python中Numpy的属性与创建矩阵
2018/09/10 Python
使用python爬取抖音视频列表信息
2019/07/15 Python
PYTHON绘制雷达图代码实例
2019/10/15 Python
Python ArgumentParse的subparser用法说明
2020/04/20 Python
简单了解如何封装自己的Python包
2020/07/08 Python
Python读取yaml文件的详细教程
2020/07/21 Python
Python爬虫逆向分析某云音乐加密参数的实例分析
2020/12/04 Python
CSS3实现点击放大的动画实例代码
2017/02/27 HTML / CSS
Wiggle澳大利亚:自行车、跑步、游泳商店
2020/11/07 全球购物
Java如何支持I18N?
2016/10/31 面试题
竞选班干部演讲稿
2014/04/24 职场文书
竞争与合作演讲稿
2014/05/12 职场文书
2014党委书记四风对照检查材料思想汇报
2014/09/21 职场文书
公务员政审材料范文
2014/12/23 职场文书
create-react-app开发常用配置教程
2022/06/25 Javascript