zoukankan      html  css  js  c++  java
  • Python装饰器的解包装(unwrap)

    在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', '
    ')
    
    
    # 测试两个装饰器情况下,能否正常解包
    @test_decorator2
    @test_decorator
    def spam2():
        print('spam2', '
    ')
    
    
    # 测试多个装饰器情况下,其中一个装饰器的包装器没有使用@wraps
    @test_decorator2
    @test_decorator3
    @test_decorator
    def spam3():
        print('spam3', '
    ')
    
    
    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', '
    ')

    而不是

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

    所以会返回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装饰的装饰器(会被当成装饰链最后一个对象处理)。
  • 相关阅读:
    Mysql优化与使用集锦
    用条件注释判断浏览器版本,解决兼容问题
    高效的使用 Response.Redirect
    JS中字符串的相关操作
    Http压力测试工具HttpTest4Net
    纯CSS(无 JavaScript)实现的响应式图像显示
    jquery使用jsonp进行跨域调用
    JS函数重载解决方案
    C# 实现将 PDF 转文本的功能
    iFrame的妙用
  • 原文地址:https://www.cnblogs.com/blackmatrix/p/6875359.html
Copyright © 2011-2022 走看看