曾灵敏 — 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. Java程序员要注意的10个问题————————好东西就是要拿来分享

    [本文来自优优码:http://www.uucode.net/201406/ten-issue-for-java],好东西就是要拿来分享 1. Array 转为 ArrayList 很多人会这么写: ...

  2. RAP开发入门-开发笔记

    一.发布/运行 每次项目发布时需要在MANIFEST.MF->bulid中勾选依赖包.文件.代码等,避免报错 部署时项目可能会报一个baseline的错误,window->preferen ...

  3. Android--PullToRefreshListView 的简单使用

    原文:  http://blog.csdn.net/lmj623565791/article/details/38238749; pull-to-refresh对ListView进行了封装,叫做:Pu ...

  4. Python: 迭代器与生成器小结

    迭代器与生成器的区别: 1. 迭代器由Class对象创建. 生成器由包含yield表达的Function对象或者Generator Expression创建. 2. 迭代器的原理: (1)由Itera ...

  5. 59.DDR3_IP核文件设置

    在ISE软件生成DDR3 IP核时,会产生很多文件,其中user_design,example_design里面分别是用户接口文件和自带的仿真测试文件.在user_design里的rtl中,这些文件是 ...

  6. 42.JTAG接口使用注意

    无论是客户反馈,还是自己亲身经历,USB-Blaster不能下载配置FPGA的情况时有出现.究其原因,大致有如下几条: 1. FPGA器件上的JTAG相关引脚出现故障: 2. USB-Blaster坏 ...

  7. oracle odbc配置

    oracle odbc配置 Win7 64位 下安装oracle odbc 不能使用控制面板中 “管理工具”->“数据源(OBDC)”中安装数据源. 而要在“ 运行” 中输入  C:\Windo ...

  8. UIScrowView swift

    // // ViewController.swift // UILabelTest // // Created by mac on 15/6/23. // Copyright (c) 2015年 fa ...

  9. P1574: [Usaco2009 Jan]地震损坏Damage

    卧槽卧槽卧槽,这道水题竟然让我WA了两遍!!评测系统卡了然后手贱又提交了一次,然后就悲催了呜呜.. 把与不能回家但牛棚完好的牛相邻的牛棚赋值为不能走(false),可以证明,如果该牛回不了家,则周围一 ...

  10. Google Volley: How to send a POST request with Json data?

    sonObjectRequest actuallyaccepts JSONObject as body. From http://arnab.ch/blog/2013/08/asynchronous- ...