曾灵敏 — APRIL 27, 2015

装饰器基本概念

大家都知道装饰器是一个很著名的设计模式,经常被用于AOP(面向切面编程)的场景,较为经典的有插入日志,性能测试,事务处理, Web权限校验Cache 等。

Python语言本身提供了装饰器语法(@),典型的装饰器实现如下:

  1. @function_wrapper
  2. def function():
  3. pass

@实际上是python2.4才提出的语法糖,针对python2.4以前的版本有另一种等价的实现:

  1. def function():
  2. pass
  3. function = function_wrapper(function)

装饰器的两种实现

函数包装器 - 经典实现

  1. def function_wrapper(wrapped):
  2. def _wrapper(*args, **kwargs):
  3. return wrapped(*args, **kwargs)
  4. return _wrapper
  5. @function_wrapper
  6. def function():
  7. pass

类包装器 - 易于理解

  1. class function_wrapper(object):
  2. def __init__(self, wrapped):
  3. self.wrapped = wrapped
  4. def __call__(self, *args, **kwargs):
  5. return self.wrapped(*args, **kwargs)
  6. @function_wrapper
  7. def function():
  8. pass

函数(function)自省

当我们谈到一个函数时,通常希望这个函数的属性像其文档上描述的那样,是被明确定义的,例如 __name__ __doc__

针对某个函数应用装饰器时,这个函数的属性就会发生变化,但这并不是我们所期望的。

  1. def function_wrapper(wrapped):
  2. def _wrapper(*args, **kwargs):
  3. return wrapped(*args, **kwargs)
  4. return _wrapper
  5. @function_wrapper
  6. def function():
  7. pass
  8. >>> print(function.__name__)
  9. _wrapper

python标准库提供了 functools.wraps() ,来解决这个问题。

  1. import functools
  2. def function_wrapper(wrapped):
  3. @functools.wraps(wrapped)
  4. def _wrapper(*args, **kwargs):
  5. return wrapped(*args, **kwargs)
  6. return _wrapper
  7. @function_wrapper
  8. def function():
  9. pass
  10. >>> print(function.__name__)
  11. function

然而,当我们想要获取被包装函数的参数( argument )或源代码( source code)时,同样不能得到我们想要的结果。

  1. import inspect
  2. def function_wrapper(wrapped): ...
  3. @function_wrapper
  4. def function(arg1, arg2): pass
  5. >>> print(inspect.getargspec(function))
  6. ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None)
  7. >>> print(inspect.getsource(function))
  8. @functools.wraps(wrapped)
  9. def _wrapper(*args, **kwargs):
  10. return wrapped(*args, **kwargs)

包装类方法(@classmethod)

当包装器( @function_wrapper )被应用于@classmethod 时,将会抛出如下异常:

  1. class Class(object):
  2. @function_wrapper
  3. @classmethod
  4. def cmethod(cls):
  5. pass
  6. Traceback (most recent call last):
  7. File "<stdin>", line 1, in <module>
  8. File "<stdin>", line 3, in Class
  9. File "<stdin>", line 2, in wrapper
  10. File ".../functools.py", line 33, in update_wrapper
  11. setattr(wrapper, attr, getattr(wrapped, attr))
  12. AttributeError: 'classmethod' object has no attribute '__module__'

因为 @classmethod 在实现时,缺少 functools.update_wrapper 需要的某些属性。这是 functools.update_wrapper 在python2中的bug,3.2版本已被修复,参考http://bugs.python.org/issue3445。

然而,在python3下执行,另一个问题出现了:

  1. class Class(object):
  2. @function_wrapper
  3. @classmethod
  4. def cmethod(cls):
  5. pass
  6. >>> Class.cmethod()
  7. Traceback (most recent call last):
  8. File "classmethod.py", line 15, in <module>
  9. Class.cmethod()
  10. File "classmethod.py", line 6, in _wrapper
  11. return wrapped(*args, **kwargs)
  12. TypeError: 'classmethod' object is not callable

这是因为包装器认定被包装的函数( @classmethod )是可以直接被调用的,但事实并不一定是这样的。被包装的函数实际上可能是描述符( descriptor ),意味着为了使其可调用,该函数(描述符)必须被正确地绑定到某个实例上。关于描述符的定义,可以参考https://docs.python.org/2/howto/descriptor.html。

总结 - 简单并不意味着正确

尽管大家实现装饰器所用的方法通常都很简单,但这并不意味着它们一定是正确的并且始终能正常工作。

如同上面我们所看到的, functools.wraps() 可以帮我们解决 __name__ __doc__ 的问题,但对于获取函数的参数(argument)或源代码( source code )则束手无策。

以上问题,wrapt都可以帮忙解决,详细用法可参考其官方文档:http://wrapt.readthedocs.org


本文作者系OneAPM工程师曾灵敏 ,想阅读更多好的技术文章,请访问OneAPM官方技术博客。

Python - 装饰器使用过程中的误区的更多相关文章

  1. python装饰器(基础中的重点)

    一.简单的装饰器 1.为什么要使用装饰器呢? 装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展 装饰器的本质:就是一个闭包函数 那么我们先来看一个简单的装饰器:实现计算每个函数的执 ...

  2. python 装饰器的坑

    今天研究了下装饰器,添加重试功能遇到了个坑,跟大家分享一下: 代码如下: def re_try(maxtry): print locals() def wrapper(fn): print local ...

  3. Python装饰器的调用过程

    在Python学习的过程中,装饰器是比较难理解的一个应用.本人也在学习期间也遇到很多坑,现将装饰器的基本调用过程总结一下. 首先,装饰器用到了“闭包”,而“闭包”是学习装饰器的基础,所以在讲装饰器之前 ...

  4. Python 装饰器装饰类中的方法

    title: Python 装饰器装饰类中的方法 comments: true date: 2017-04-17 20:44:31 tags: ['Python', 'Decorate'] categ ...

  5. python装饰器中@wraps作用--修复被装饰后的函数名等属性的改变

    Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的de ...

  6. python装饰器在接口自动化测试中的应用

    在讲解装饰器在接口自动化测试项目的应用之前,我们先来介绍一下python装饰器到底是个什么 装饰器 说装饰器就不得不提一下函数这个一等公民了,在python中函数有几个特性先来了解一下 函数的一些特性 ...

  7. python装饰器通俗易懂的解释!

    1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...

  8. Python装饰器由浅入深

    装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...

  9. python 装饰器、内部函数、闭包简单理解

    python内部函数.闭包共同之处在于都是以函数作为参数传递到函数,不同之处在于返回与调用有所区别. 1.python内部函数 python内部函数示例: def test(*args): def a ...

随机推荐

  1. ROS多个master消息互通

    需求 有时候我们需要有几个不同的master, 他们之间要交换topic的内容,这时候就不能使用ros自带的设置同一个master的方法. 我们的处理方法是,构造一个client和一个server,他 ...

  2. 为啥 Objective-C 使用中括号来调用类方法?

    原因在这篇文章中:http://stackoverflow.com/questions/23723838/why-does-objective-c-use-square-brackets-for-me ...

  3. 如何在Eclipse中配置Tomcat

    1.Eclipse EE 配置Tomcat Eclipse EE 主要用于Java Web开发和J2EE项目开发.Eclipse EE中配置Tomcat比较简单,新建一个Tomcat Server即可 ...

  4. java读取各类型的文件

    java读取各类型的文件 用到的几个包 bcmail-jdk14-132.jar/bcprov-jdk14-132.jar/checkstyle-all-4.2.jar/FontBox-0.1.0-d ...

  5. hdu 4609 3-idiots <FFT>

    链接: http://acm.hdu.edu.cn/showproblem.php?pid=4609 题意: 给定 N 个正整数, 表示 N 条线段的长度, 问任取 3 条, 可以构成三角形的概率为多 ...

  6. Android之Activity的几种跳转方式

     1.显示调用方法 Intent intent=new Intent(this,OtherActivity.class);  //方法1 Intent intent2=new Intent(); in ...

  7. df du

    df命令详细用法 a:显示全部的档案系统和各分割区的磁盘使用情形 i:显示i -nodes的使用量 k:大小用k来表示 (默认值) t:显示某一个档案系统的所有分割区磁盘使用量 x:显示不是某一个档案 ...

  8. 面向对象原生js幻灯片代淡出效果

    面向对象原生js幻灯片代淡出效果 下面是代码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" & ...

  9. 浅析Objective-C字面量

    编写Objective-C程序时,总会用到某几个类,它们属于Foundation框架.虽然从技术上来说,不用Foundation框架也能写出Objective-C代码,但实际上却经常要用到此框架.这几 ...

  10. Linux虚拟机配置本地yum源

    刚开始使用Linux,自己构建了一个Linux虚拟机之后,在使用yum install的时候,经常是出错,提示连接不上. 一直以为是自己构建的虚拟机的问题,后来在网上查找了一些资料,才发现:需要配置本 ...