对于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采用socket模拟TCP通讯的实现方法
Nov 19 Python
分析在Python中何种情况下需要使用断言
Apr 01 Python
Python回调函数用法实例详解
Jul 02 Python
python时间日期函数与利用pandas进行时间序列处理详解
Mar 13 Python
Python实现爬取百度贴吧帖子所有楼层图片的爬虫示例
Apr 26 Python
Python3实现统计单词表中每个字母出现频率的方法示例
Jan 28 Python
Python如何优雅获取本机IP方法
Nov 10 Python
python中seaborn包常用图形使用详解
Nov 25 Python
Python txt文件如何转换成字典
Nov 03 Python
Requests什么的通通爬不了的Python超强反爬虫方案!
May 20 Python
Python字符串的转义字符
Apr 07 Python
使用Python开发冰球小游戏
Apr 30 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代码把全角数字转为半角数字
2007/12/10 PHP
zf框架的校验器使用使用示例(自定义校验器和校验器链)
2014/03/13 PHP
php显示指定目录下子目录的方法
2015/03/20 PHP
解决微信授权回调页面域名只能设置一个的问题
2016/12/11 PHP
thinkPHP5.0框架整体架构总览【应用,模块,MVC,驱动,行为,命名空间等】
2017/03/25 PHP
CentOS系统中PHP安装扩展的方式汇总
2017/04/09 PHP
微信企业转账之入口类分装php代码
2018/10/01 PHP
PHP后台备份MySQL数据库的源码实例
2019/03/18 PHP
jquery 屏蔽一个区域内的所有元素,禁止输入
2009/10/22 Javascript
javascript对select标签的控制(option选项/select)
2013/01/31 Javascript
jquery.post用法关于type设置问题补充
2014/01/03 Javascript
jQuery简易图片放大特效示例代码
2014/06/09 Javascript
用C/C++来实现 Node.js 的模块(二)
2014/09/24 Javascript
jquery datatable服务端分页
2016/08/31 Javascript
浅谈AngularJS中ng-class的使用方法
2016/11/11 Javascript
JavaScript结合HTML DOM实现联动菜单
2017/04/05 Javascript
js中this的指向问题归纳总结
2018/11/28 Javascript
详解在Angular4中使用ng2-baidu-map的方法
2019/06/19 Javascript
k8s node节点重新加入master集群的实现
2021/02/22 Javascript
用Python实现斐波那契(Fibonacci)函数
2016/03/25 Python
python中如何使用正则表达式的非贪婪模式示例
2017/10/09 Python
删除python pandas.DataFrame 的多重index实例
2018/06/08 Python
Python使用ConfigParser模块操作配置文件的方法
2018/06/29 Python
python中return不返回值的问题解析
2020/07/22 Python
Opencv python 图片生成视频的方法示例
2020/11/18 Python
用HTML5制作一个简单的弹力球游戏
2015/05/12 HTML / CSS
韩国家庭购物网上商店:Nsmall
2017/05/07 全球购物
美国折扣地毯销售网站:Rugs.com
2020/03/27 全球购物
《我的第一本书》教学反思
2014/02/15 职场文书
元旦红领巾广播稿
2014/02/19 职场文书
农民工工资发放承诺书
2014/03/31 职场文书
连带责任保证书
2014/04/29 职场文书
禁烟标语大全
2014/06/11 职场文书
师德师风的心得体会
2014/09/02 职场文书
赔偿协议书
2015/01/27 职场文书
单位综合评价意见
2015/06/05 职场文书