对于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 Trie树实现字典排序
Mar 28 Python
解决Python传递中文参数的问题
Aug 04 Python
简单掌握Python的Collections模块中counter结构的用法
Jul 07 Python
Python 中 list 的各项操作技巧
Apr 13 Python
Python3处理HTTP请求的实例
May 10 Python
对python3 一组数值的归一化处理方法详解
Jul 11 Python
Ubuntu下Python2与Python3的共存问题
Oct 31 Python
详解分布式任务队列Celery使用说明
Nov 29 Python
Pandas的read_csv函数参数分析详解
Jul 02 Python
Python如何通过百度翻译API实现翻译功能
Apr 02 Python
Keras - GPU ID 和显存占用设定步骤
Jun 22 Python
Python OpenCV快速入门教程
Apr 17 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/06/14 PHP
CodeIgniter辅助之第三方类库third_party用法分析
2016/01/20 PHP
PHP使用mysqli操作MySQL数据库的简单方法
2017/02/04 PHP
jQuery学习笔记 操作jQuery对象 CSS处理
2012/09/19 Javascript
JavaScript_ECMA5数组新特性详解
2016/06/12 Javascript
原生js实现焦点轮播图效果
2017/01/12 Javascript
微信小程序商城项目之购物数量加减(3)
2017/04/17 Javascript
Angular 1.x个人使用的经验小结
2017/07/19 Javascript
详解Vuex中mapState的具体用法
2017/09/28 Javascript
vue组件生命周期详解
2017/11/07 Javascript
关于vue面试题汇总
2018/03/20 Javascript
vue实现前台列表数据过滤搜索、分页效果
2019/05/28 Javascript
微信小程序如何引用外部js,外部样式,公共页面模板
2019/07/23 Javascript
javascript实现点击星星小游戏
2019/12/24 Javascript
基于vue3.0.1beta搭建仿京东的电商H5项目
2020/05/06 Javascript
深入讲解Python中的迭代器和生成器
2015/10/26 Python
Python基础语言学习笔记总结(精华)
2017/11/14 Python
python使用os.listdir和os.walk获得文件的路径的方法
2017/12/16 Python
Python设计模式之装饰模式实例详解
2019/01/21 Python
python定时复制远程文件夹中所有文件
2019/04/30 Python
解决python xx.py文件点击完之后一闪而过的问题
2019/06/24 Python
使用Python刷淘宝喵币(低阶入门版)
2019/10/30 Python
在Ubuntu 20.04中安装Pycharm 2020.1的图文教程
2020/04/30 Python
CSS3实现线性渐变用法示例代码详解
2020/08/07 HTML / CSS
10种CSS3实现的loading动画,挑一个走吧?
2020/11/16 HTML / CSS
快速实现一个简单的canvas迷宫游戏的示例
2018/07/04 HTML / CSS
AmazeUI 列表的实现示例
2020/08/17 HTML / CSS
美国最顶级的精品店之一:Hampden Clothing
2016/12/22 全球购物
业务代表的岗位职责
2013/11/16 职场文书
拓展训练激励口号
2014/06/17 职场文书
党员思想汇报材料
2014/12/19 职场文书
小兵张嘎电影观后感
2015/06/03 职场文书
运动会通讯稿600字
2015/07/20 职场文书
MySQL infobright的安装步骤
2021/04/07 MySQL
python基础之错误和异常处理
2021/10/24 Python
Python中如何处理常见报错
2022/01/18 Python