在Python 3.4 中,新增一个方法unwrap,用于将被装饰的函数,逐层进行解包装。

inspect.unwrap(func, *, stop=None)

unwrap方法接受两个参数:func为需要解包装的函数;stop接受一个单参数的函数,作为回调函数,每次会将即将解包装的函数对象传入到回调函数中,如果回调函数返回True,则会提前终止解包,如果没有返回值,或返回Flase,unwrap会逐层解包直至装饰链中的最后一个函数对象。

另外能正常解包装的前提是装饰器上添加了@wraps(func)装饰器。

写一些demo运行看看结果

完整代码: https://github.com/blackmatrix7/python-learning/blob/master/other/unwrap.py

首先定义一些没什么用的函数和装饰器,以及回调函数。

这里每个函数和装饰器都会打印自身的名字,以此标记此函数或装饰器是否被运行。而回调函数会执行传入的函数对象,通过函数对象自身的打印功能,显示回调函数接受函数对象的顺序。

from inspect import unwrap
from functools import wraps __author__ = 'blackmatrix' def test_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('test_decorator')
return func(*args, **kwargs)
return wrapper def test_decorator2(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('test_decorator2')
return func(*args, **kwargs)
return wrapper def test_decorator3(func):
def wrapper(*args, **kwargs):
print('test_decorator3')
return func(*args, **kwargs)
return wrapper # 测试只有一个装饰器的情况下,能否正常解包
@test_decorator
def spam():
print('spam1', '\n') # 测试两个装饰器情况下,能否正常解包
@test_decorator2
@test_decorator
def spam2():
print('spam2', '\n') # 测试多个装饰器情况下,其中一个装饰器的包装器没有使用@wraps
@test_decorator2
@test_decorator3
@test_decorator
def spam3():
print('spam3', '\n') def callback(obj):
obj() def callback2(obj):
if obj is spam2:
print(True)
return True

调用被装饰的函数spam

spam(value='decorated_spam')

装饰器生效,输出 test_decorator,证明装饰器是能正常工作的。

test_decorator
spam1

使用 unwrap 获取被包装的函数,命名为unwrap_spam,并调用

unwrap_spam = unwrap(spam)
unwrap_spam()

调用已经解包装的函数命名为unwrap_spam,没有输出 test_decorator,(根据上面述说的规则,没有打印装饰器名称,就认为没有执行,所以装饰器没有生效,后面的测试也是相同的判定方式,不再复述)。

说明解包是成功的,成功获取到被装饰的函数(而不是被装饰后的函数)。

spam1

接着试试多个装饰器的情况下,能否正常解包

unwrap_spam2 = unwrap(spam2)
unwrap_spam2()

同样,调用解包后的函数,两个装饰器都没有输出结果,说明多个装饰器也能正常解包

spam2

之前能正常解包的一个前提是装饰器本身的包装函数上,添加了系统内部的装饰器@wraps(func)。

现在我们来试试如果使用了未添加@wraps的装饰器,能否正常解包。

之前定义了一个test_decorator3装饰器,没有添加@wraps。

现在将test_decorator2、test_decorator3、test_decorator三个装饰器逐个添加到函数spam3上(注意test_decorator2在test_decorator3之前)。

尝试解包

unwrap_spam3 = unwrap(spam3)
unwrap_spam3()

从运行结果可以看出,运行解包后的函数unwrap_spam3,test_decorator2没有被打印出来,说明被成功解包,而装饰器test_decorator3、test_decorator都正常运行(都正常打印值),并没有解包成功。

test_decorator3
test_decorator
spam3

所以,多个装饰器情况下,如果其中一个装饰器没有使用@wraps,那么在装饰链中,unwrap只能解包到未使用@wraps的装饰器。

接着,测试一下回调函数的调用。

再提一下,之前提过我们设定的回调函数会执行传入的函数对象,通过函数对象自身的打印功能,显示回调函数接受函数对象的顺序。

unwrap_spam2_unwrap = unwrap(spam2, stop=callback)

输出结果

test_decorator2
test_decorator
spam2 test_decorator
spam2

这个输出结果分两部分,第一部分为

test_decorator2
test_decorator
spam2

这是第一次解包的运行结果,解包此时调用依次调用test_decorator2、test_decorator、spam2所以会由如上的输出结果。

下面是第二部分的输出,这是第二次解包的运行结果,此时回调函数的调用顺序是test_decorator、spam2,所以会输出下面的结果

test_decorator
spam2

而第三次解包,因为已经获取到装饰链的最后一个对象,所以不会再调用回调函数,所以不会有输出结果。

另外还需要再注意下第一次解包的输出结果:test_decorator2、test_decorator、spam2。

说明第一次解包时,传给回调函数是完整的,整个被test_decorator2、test_decorator两个装饰器装饰后的spam2!

而不是解包后的函数对象(如果是解包后的函数对象,因为test_decorator2已经成功解包,所以打印结果应该是test_decorator、spam2)!

换个方法验证,在回调函数中添加 print(obj is spam2) 的判断,也会得到True的结果。

def callback(obj):
obj()
print(obj is spam2)

上面用于判断的spam2是被test_decorator2、test_decorator两个装饰器装饰后的spam2,而不是被装饰的函数spam2,听起来有点拗口,用代码看下就很清楚了:

spam2实际上是

@test_decorator2
@test_decorator
def spam2():
print('spam2', '\n')

而不是

def spam2():
print('spam2', '\n')

所以会返回True

也就是说,传给回调函数的对象,实际上应该是本次待解包的函数对象,而不是解包之后的函数对象

再试试回调函数返回True的时候是不是真的可以提前终止解包。

unwrap_spam2 = unwrap(spam2, stop=callback2)

执行结果如下,也就是在第一次调用回调函数,返回True时,解包就已经终止。

test_decorator2
test_decorator
spam2

那么第一次解包终止后,获得的函数对象unwrap_spam2又是什么?运行看看,得到如下输出:

test_decorator2
test_decorator
spam2

说明,第一次解包终止后,得到的函数对象实际上是未解包的函数对象,即完整被test_decorator2、test_decorator两个装饰器装饰后的spam2。

所以,整个解包装的流程应该是这样的

将当前待解包的函数对象传入回调函数中 --> 回调函数返回 True,解包终止,返回当前待解包的函数对象

                   --> 回调函数返回 False或者没有返回值,解包继续,返回解包后的函数对象

其中:

  1. 如果解包后的函数对象不是装饰链中的最后一个对象,那么重复第一步,将对象传入回调函数
  2. 如果解包后的函数对象已经是装饰链的最后一个对象(或是一个没有用@wrap装饰的装饰器),那么解包终止。
  3. 如果没有回调函数,则逐层解包,直至装饰链最后一个对象,或遇到一个没有用@wrap装饰的装饰器(会被当成装饰链最后一个对象处理)。

Python装饰器的解包装(unwrap)的更多相关文章

  1. Python装饰器详解

    python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...

  2. Python—装饰器详解

    装饰器:(语法糖) 本质是函数,它是赋予函数新功能,但是不改变函数的源代码及调用方式   原则: 1.不能修改被装饰函数的源代码 2.不能修改被装饰函数的调用方式 3.函数的返回值也不变 这两点简而言 ...

  3. Python装饰器粗解学习

    此次学习资料详细来自:http://blog.csdn.net/u013471155 本次是粗学,仍有诸多疑问,暂且记录一二,如有不足和建议,希望可以达者指点. 三个关键点理解:   1.关于函数“变 ...

  4. python设计模式之装饰器详解(三)

    python的装饰器使用是python语言一个非常重要的部分,装饰器是程序设计模式中装饰模式的具体化,python提供了特殊的语法糖可以非常方便的实现装饰模式. 系列文章 python设计模式之单例模 ...

  5. Python中的各种装饰器详解

    Python装饰器,分两部分,一是装饰器本身的定义,一是被装饰器对象的定义. 一.函数式装饰器:装饰器本身是一个函数. 1.装饰函数:被装饰对象是一个函数 [1]装饰器无参数: a.被装饰对象无参数: ...

  6. python装饰器(docorator)详解

    引言: 装饰器是python面向对象编程三大器之一,另外两个迭代器.生成器只是我现在还没有遇到必须使用的场景,等确实需要用到的时候,在补充资料:装饰器在某些场景真的是必要的,比如定义了一个类或者一个函 ...

  7. Python全栈开发之8、装饰器详解

    一文让你彻底明白Python装饰器原理,从此面试工作再也不怕了.转载请注明出处http://www.cnblogs.com/Wxtrkbc/p/5486253.html 一.装饰器 装饰器可以使函数执 ...

  8. python函数装饰器详解

    python装饰器(fuctional decorators)简单来说就是修改其他函数的函数. 这样的函数需要满足两个个条件: 1.不能修改原函数的源代码 2.不能改变原函数的调用方式 需要达到的效果 ...

  9. (转)面向对象(深入)|python描述器详解

    原文:https://zhuanlan.zhihu.com/p/32764345 https://www.cnblogs.com/aademeng/articles/7262645.html----- ...

随机推荐

  1. TensorFlow 处理图片

    目标:介绍如何对图像数据进行预处理使训练得到的神经网络模型尽可能小地被无关因素所影响.但与此同时,复杂的预处理过程可能导致训练效率的下降.为了减少预处理对于训练速度的影响,TensorFlow 提供了 ...

  2. 海尔U+的启发:让用户对智能家居拥有“话语权”

        近年来,智能家居成了IT产业的重要话题,随着智能家电一系列产品的出现,智能家居最终開始从概念走向落地.只是,眼下智能家居行业有个非常突出的问题------因为缺乏开放的意识,不管是产品还是理念 ...

  3. BNUOJ34977夜空中最亮的星(数学,向量的应用)

    夜空中最亮的星 Time Limit: 2000ms Memory Limit: 65536KB 64-bit integer IO format: %lld      Java class name ...

  4. HDU 4193 Non-negative Partial Sums(想法题,单调队列)

    HDU 4193 题意:给n个数字组成的序列(n <= 10^6).求该序列的循环同构序列中,有多少个序列的随意前i项和均大于或等于0. 思路: 这题看到数据规模认为仅仅能用最多O(nlogn) ...

  5. Spring MVC新手教程(二)

    第一篇文章宏观讲了Spring MVC概念,以及分享了一个高速入门的样例. 这篇文章主要来谈谈Spring MVC的配置文件. 首先来谈谈web.xml: web项目启动时自己主动载入到内存中的信息, ...

  6. c++中虚多态的实现机制

    c++中虚多态的实现机制 參考博客:http://blog.csdn.net/neiloid/article/details/6934135 序言 证明vptr指针存在 无继承 单继承无覆盖 单继承有 ...

  7. Hibernate常见问题 No row with the given identifier exists问题的解决办法及解决

    (1)在学习Hibernate的时候遇到了这个问题"No row with the given identifier exists"在网上一搜看到非常多人也遇到过这个问题! 问题的 ...

  8. 前端(各种demo)三:优惠券,热区,等模块的实现(css方式)

    各种样式的css实现 1.优惠券样式的实现: 2.热区的实现:   在电商平台上总会发出各种券,需要对应到不同的产品,对应到不同的服务.而使用券可以使用UED的设计稿里的照片,但是本来一次性的加载过多 ...

  9. 史上最强学生管理系统之IO版

    既上一博发布的ArrayList版本之后,新一版的IO版又来了,其实只是在上一个版本里面添加了IO流的内容,将存入更改的信息更新到了文件中而已,这个版本网上仍然很多,本人只是在某些方面稍加修改,因为自 ...

  10. websocket简介

    在我们做web项目的过程中,经常需要做的一个模块是消息模块.典型的场景:一个商城系统的后台管理,我想实现如果前台有客户下单,后台就会接到消息,以便尽快发货处理. 要实现上述的功能,我们有几种备选的方案 ...