对于Python装饰器使用的一些建议


Posted in Python onJune 03, 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 )则束手无策。

Python 相关文章推荐
python 正则表达式 概述及常用字符
May 04 Python
Python脚本实现格式化css文件
Apr 08 Python
python查看zip包中文件及大小的方法
Jul 09 Python
python统计字母、空格、数字等字符个数的实例
Jun 29 Python
python实现求两个字符串的最长公共子串方法
Jul 20 Python
python判断所输入的任意一个正整数是否为素数的两种方法
Jun 27 Python
python把ipynb文件转换成pdf文件过程详解
Jul 09 Python
python lambda表达式(匿名函数)写法解析
Sep 16 Python
浅谈python print(xx, flush = True) 全网最清晰的解释
Feb 21 Python
利用Python实现字幕挂载(把字幕文件与视频合并)思路详解
Oct 21 Python
python中的时区问题
Jan 14 Python
PyMongo 查询数据的实现
Jun 28 Python
Python模块搜索概念介绍及模块安装方法介绍
Jun 03 #Python
Python使用ftplib实现简易FTP客户端的方法
Jun 03 #Python
Python中的深拷贝和浅拷贝详解
Jun 03 #Python
python下paramiko模块实现ssh连接登录Linux服务器
Jun 03 #Python
python处理二进制数据的方法
Jun 03 #Python
Python读写配置文件的方法
Jun 03 #Python
python操作ssh实现服务器日志下载的方法
Jun 03 #Python
You might like
php实现从ftp服务器上下载文件树到本地电脑的程序
2009/02/10 PHP
PHP 杂谈《重构-改善既有代码的设计》之三 重新组织数据
2012/04/09 PHP
可缩放Reloaded-一个针对可缩放元素的复用组件
2007/03/10 Javascript
学习ExtJS 访问容器对象
2009/10/07 Javascript
js 显示base64编码的二进制流网页图片
2014/04/04 Javascript
jquery中cookie用法实例详解(获取,存储,删除等)
2016/01/04 Javascript
JAVA Web实时消息后台服务器推送技术---GoEasy
2016/11/04 Javascript
vue.js从安装到搭建过程详解
2017/03/17 Javascript
Three.js开发实现3D地图的实践过程总结
2017/11/20 Javascript
vue-cli开发时,关于ajax跨域的解决方法(推荐)
2018/02/03 Javascript
JS代码实现电脑配置检测功能
2018/03/21 Javascript
LayUI表格批量删除方法
2018/08/15 Javascript
vue里面v-bind和Props 利用props绑定动态数据的方法
2018/08/27 Javascript
jQuery实现获取及设置CSS样式操作详解
2018/09/05 jQuery
vue文件运行的方法教学
2019/02/12 Javascript
微信小程序调用微信支付接口的实现方法
2019/04/29 Javascript
ES6中定义类和对象的方法示例
2019/07/31 Javascript
python聊天程序实例代码分享
2013/11/18 Python
Python中for循环详解
2014/01/17 Python
python打开网页和暂停实例
2014/09/30 Python
对Python3.6 IDLE常用快捷键介绍
2018/07/16 Python
Django使用unittest模块进行单元测试过程解析
2019/08/02 Python
纯css3实现思维导图样式示例
2018/11/01 HTML / CSS
HTML5 拖拽批量上传文件的示例代码
2018/03/28 HTML / CSS
深入理解HTML的FormData对象
2016/05/17 HTML / CSS
局域网标准
2016/09/10 面试题
介绍一些UNIX常用简单命令
2014/11/11 面试题
学校安全责任书
2014/04/14 职场文书
综合素质自我评价怎么写
2014/09/14 职场文书
学生旷课检讨书500字
2014/10/28 职场文书
团日活动总结格式
2015/05/11 职场文书
事业单位工作人员岗前培训心得体会
2016/01/08 职场文书
2016年基层党组织公开承诺书
2016/03/25 职场文书
承诺书应该怎么写?
2019/09/10 职场文书
正确的理解和使用Django信号(Signals)
2021/04/14 Python
Go语言编译原理之源码调试
2022/08/05 Golang