对于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 文件重命名工具代码
Jul 26 Python
python编写的最短路径算法
Mar 25 Python
Python logging管理不同级别log打印和存储实例
Jan 19 Python
详解用TensorFlow实现逻辑回归算法
May 02 Python
Python之批量创建文件的实例讲解
May 10 Python
浅析PHP与Python进行数据交互
May 15 Python
Python3爬虫全国地址信息
Jan 05 Python
Python QQBot库的QQ聊天机器人
Jun 19 Python
Python编程实现tail-n查看日志文件的方法
Jul 08 Python
python Tensor和Array对比分析
Jan 08 Python
简单的Python人脸识别系统
Jul 14 Python
Python Flask实现进度条
May 11 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来自动调用不同服务器上的flash
2006/10/09 PHP
对javascript和select部件的结合运用
2006/10/09 PHP
PHP类中的魔术方法(Magic Method)简明总结
2014/07/08 PHP
Thinkphp5 微信公众号token验证不成功的原因及解决方法
2017/11/12 PHP
Javascript的IE和Firefox兼容性汇编
2006/07/01 Javascript
HTML-CSS群中单选引发的“事件”
2007/03/05 Javascript
JavaScript 学习笔记(五)
2009/12/31 Javascript
Jquery从头学起第四讲 jquery入门教程
2010/08/01 Javascript
原生js实现跨浏览器获取鼠标按键的值
2013/04/08 Javascript
指定区域的图片自动按比例缩小的js代码(防止页面被图片撑破)
2014/02/21 Javascript
JQuery实现当鼠标停留在某区域3秒后自动执行
2014/09/09 Javascript
jquery获得当前html页面源码的方法
2015/07/14 Javascript
用jquery获取自定义的标签属性的值简单实例
2016/09/17 Javascript
AngularJS实现根据变量改变动态加载模板的方法
2016/11/04 Javascript
ionic2懒加载配置详解
2017/09/01 Javascript
vue中各选项及钩子函数执行顺序详解
2018/08/25 Javascript
微信小程序扫描二维码获取信息实例详解
2019/05/07 Javascript
[51:36]EG vs VP 2018国际邀请赛淘汰赛BO3 第一场 8.24
2018/08/25 DOTA
状态机的概念和在Python下使用状态机的教程
2015/04/11 Python
Python爬豆瓣电影实例
2018/02/23 Python
python  创建一个保留重复值的列表的补码
2018/10/15 Python
python实现的生成word文档功能示例
2019/08/23 Python
python+opencv3生成一个自定义纯色图教程
2020/02/19 Python
python模拟斗地主发牌
2020/04/22 Python
解决Keras 自定义层时遇到版本的问题
2020/06/16 Python
python 爬虫爬取京东ps4售卖情况
2020/12/18 Python
常用的HTML5列表标签
2017/06/20 HTML / CSS
Hotter Shoes美国官网:英国最受欢迎的舒适鞋
2018/08/02 全球购物
Linux管理员面试题 Linux admin interview questions
2016/07/08 面试题
小学兴趣小组活动总结
2014/07/07 职场文书
公安机关纪律作风整顿剖析
2014/10/10 职场文书
2014年办公室文员工作总结
2014/11/12 职场文书
检讨书怎么写
2015/05/07 职场文书
python requests模块的使用示例
2021/04/07 Python
PHP实现考试倒计时功能代码
2021/04/16 PHP
Java中try catch处理异常示例
2021/12/06 Java/Android