对于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中 os._exit() 和 sys.exit(), exit(0)和exit(1) 的用法和区别
Jun 23 Python
Python使用三种方法实现PCA算法
Dec 12 Python
python实现二叉查找树实例代码
Feb 08 Python
Python cookbook(数据结构与算法)筛选及提取序列中元素的方法
Mar 19 Python
Python实现定时自动关闭的tkinter窗口方法
Feb 16 Python
解决python3.5 正常安装 却不能直接使用Tkinter包的问题
Feb 22 Python
django echarts饼图数据动态加载的实例
Aug 12 Python
QML用PathView实现轮播图
Jun 03 Python
Python装饰器如何实现修复过程解析
Sep 05 Python
python使用正则表达式匹配txt特定字符串(有换行)
Dec 09 Python
python中的装饰器该如何使用
Jun 18 Python
Python matplotlib绘制雷达图
Apr 13 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会话(Session)实现用户登陆功能
2013/06/29 PHP
YII实现分页的方法
2014/07/09 PHP
php+mysql实现用户注册登陆的方法
2015/01/03 PHP
php5.3后静态绑定用法详解
2016/11/11 PHP
让whoops帮我们告别ThinkPHP6的异常页面
2020/03/02 PHP
JavaScript 语法集锦 脚本之家基础推荐
2009/11/15 Javascript
jQuery链式操作如何实现以及为什么要用链式操作
2013/01/17 Javascript
js中style.display=&quot;&quot;无效的解决方法
2014/10/30 Javascript
JavaSciprt中处理字符串之sup()方法的使用教程
2015/06/08 Javascript
10个很棒的jQuery代码片段
2015/09/24 Javascript
Javascript封装id、class与元素选择器方法示例
2017/03/13 Javascript
通过fastclick源码分析彻底解决tap“点透”
2017/12/24 Javascript
可能被忽略的一些JavaScript数组方法细节
2019/02/28 Javascript
Vue中的组件及路由使用实例代码详解
2019/05/22 Javascript
关于Layui Table隐藏列问题
2019/09/16 Javascript
小程序识别身份证,银行卡,营业执照,驾照的实现
2019/11/05 Javascript
原生javascript的ajax请求及后台PHP响应操作示例
2020/02/24 Javascript
Vue快速实现通用表单验证的方法
2020/02/24 Javascript
[45:52]完美世界DOTA2联赛PWL S3 Forest vs INK ICE 第二场 12.09
2020/12/12 DOTA
python使用flask与js进行前后台交互的例子
2019/07/19 Python
Python必须了解的35个关键词
2020/07/16 Python
基于html5 DeviceOrientation 实现微信摇一摇功能
2015/09/25 HTML / CSS
美体小铺美国官网:The Body Shop美国
2017/11/10 全球购物
Roxy荷兰官方网站:冲浪、滑雪板、服装和配件
2019/10/22 全球购物
经典优秀毕业生求职信范文分享
2013/12/18 职场文书
出纳员的岗位职责
2014/02/22 职场文书
亲子读书活动方案
2014/02/22 职场文书
幼儿园保育员岗位职责
2014/04/13 职场文书
优秀党支部书记事迹材料
2014/05/29 职场文书
航空学院求职信
2014/06/11 职场文书
银行奉献演讲稿
2014/09/16 职场文书
影视后期实训报告
2014/11/05 职场文书
2015年护士节活动策划方案
2015/05/04 职场文书
2015年教师业务工作总结
2015/05/26 职场文书
用Python实现Newton插值法
2021/04/17 Python
Go 内联优化让程序员爱不释手
2022/06/21 Golang