曾灵敏 — APRIL 27, 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 )则束手无策。

以上问题,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. c语言内存分配-malloc

    malloc 原型:(原来返回类型是char) extern void *malloc(unsigned int num_bytes); 头文件: #include <stdlib.h> ...

  2. 007-python基础-pyc是什么

    3.1 解释型语言和编译型语言 计算机是不能够识别高级语言的,所以当我们运行一个高级语言程序的时候,就需要一个"翻译机"来从事把高级语言转变成计算机能读懂的机器语言的过程.这个过程 ...

  3. python-抓取图片

    今天看到博客园一个文章,python抓取图片,也没看内容,心想自己也写一个抓取脚本试试看,一方面自己也在学习python,另一方面毕竟实际工作也经常会遇到这种需要临时写脚本的时候,突击锻炼还是好的嘛. ...

  4. Python学习教程(learning Python)--2.3.1 Python传参函数设计

    本节主要讨论设计传递多个参数子函数的设计方法. 在2.3节里我们讨论了如何自己设计一个带参数的子函数的设计方法,现在我们研究一下如何传递两个及以上参数的设计方法. 函数为何要带参数呢?其实原因很简单, ...

  5. 一个朋友js图表开发遇到的问题 解决思路c和js

    引言            不求知道一切, 只求发现一件 -- 乔治·西蒙·欧姆 附注:那些存在于梦幻中的事迹,那些儿时梦中的人物,每每看起,都觉得 .哎 .... 岁月 ... 一直在努力 ...  ...

  6. [JAVA][RCP] Eclipse4/RCP/Lifecycle

    E4AP provides two levels of lifecycles, for contributions and for the application. Contents [hide]  ...

  7. 惊曝6.24AppCan移动开发大会参展名录,现场礼品超多!

    AppCan移动开发者大会召开在即, 诸位参展商准备就绪, 移动圈的半边天都来了, 现场活动和礼品多到爆炸, 请大家一一过目! 排名不分先后,AppCan不偏心! 1.极验验证 首创滑动式拼图验证码. ...

  8. DrawerLayout带有侧滑功能的布局类(1)

    DrawerLayout: DrawerLayout顾名思义就是一个管理布局的.使用方式可以与其它的布局类类似. DrawerLayout带有滑动的功能.只要按照drawerLayout的规定布局方式 ...

  9. hdu 3530 Subsequence

    题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=3530 Subsequence Description There is a sequence of i ...

  10. 团队开发——Alpha版总结会议

    本组目前存在的问题: 1.在选题的时候,题目选的比较有难度,造成后期工作量较大,实现有难度(未能正确估计项目的难度). 2.最初规划时,设计的功能较多,但是技术水平达不到,导致目前完成功能较少. 3. ...