python中装饰背后的机制

时间:2017-09-23 15:03:00

标签: python python-3.x python-decorators

下面是python中装饰器的一个例子。我不太了解它对装饰双重的装饰者的实际效果。

from functools import update_wrapper
def decorator(d):
    print(d.__name__)
    return lambda fn: update_wrapper(d(fn),fn)

decorator=decorator(decorator) #I don't understand how this works.

@decorator
def n_ary(f):
    print(f.__name__)
    def n_ary_f(x,*args):               
        return x if not args else f(x,n_ary_f(*args))
    return n_ary_f

@n_ary
def seq(x,y):return ('seq',x,y)

似乎流程应该是(我不确定):

  1. decorator已修饰,因此返回lambda fn: update_wrapper(decorator(fn),fn)

  2. n_ary=decorator(n_ary),由于n_ary

  3. 的功能,update_wrapper(decorator(n_ary),n_ary)现已更新
  4. 第三部分应该是seq的更新,但我不明白何时使用update_wrapper函数。

1 个答案:

答案 0 :(得分:7)

装饰只是用于调用另一个函数的语法糖,并用结果替换当前的函数对象。你试图理解的decorator舞蹈过度使用了这个事实。尽管它试图让生成装饰器变得更容易,但我发现实际上并没有添加任何东西,只是因为不遵循标准做法而产生混淆。

要了解正在发生的事情,您可以使用返回值替换函数调用(包括正在应用的装饰器),并通过想象保存对原始修饰函数对象的引用来跟踪d引用:

  1. decorator=decorator(decorator)通过调用自身替换原始decorator函数。我们将忽略此处的print()调用以使替换更容易。

    decorator(decorator)调用返回lambda fn: update_wrapper(d(fn),fn),其中d绑定到原始版本 decorator,现在我们已经

    _saved_reference_to_decorator = decorator
    decorator = lambda fn: update_wrapper(_saved_reference_to_decorator(fn), fn)
    

    所以update_wrapper()实际上还没有被调用。只有在调用这个新的decorator lambda时才会调用它。

  2. @decorator然后调用上面的lambda(一个调用_saved_reference_to_decorator(fr)并将结果传递给update_wrapper())并将该lambda应用于def n_ary(f)功能:

    n_ary = decorator(n_ary)
    

    扩展为:

    n_ary = update_wrapper(_saved_reference_to_decorator(n_ary), n_ary)
    

    是:

    _saved_reference_to_n_ary = n_ary
    n_ary = update_wrapper(lambda fn: update_wrapper(_saved_reference_to_n_ary(fn), fn), n_ary)
    

    现在,update_wrapper()只是将元数据从第二个参数复制到第一个参数返回第一个参数,然后离开:

    n_ary = lambda fn: update_wrapper(_saved_reference_to_n_ary(fn), fn)
    

    __name__并在lambda函数对象上设置此类。

  3. @n_ary又是一个正在应用的装饰器,这次是def seq(x, y),所以我们得到:

    seq = n_ary(seq)
    

    可以扩展为:

    seq = update_wrapper(_saved_reference_to_n_ary(seq), seq)
    

    如果我们将update_wrapper()的返回值设为

    seq = _saved_reference_to_n_ary(seq)
    

    将元数据从原始seq复制到原始n_ary函数返回的任何内容。

  4. 所以最后,所有这些舞蹈都会让你update_wrapper()被应用于装饰器的返回值,这是包含的包装函数。

    这是一切,太复杂了update_wrapper()函数具有更易读的辅助装饰器:@functools.wraps()。您的代码可以重写为:

    import functools
    
    def n_ary(f):
        print(f.__name__)
        @functools.wraps(f)
        def n_ary_f(x,*args):
            return x if not args else f(x,n_ary_f(*args))
        return n_ary_f
    
    @n_ary
    def seq(x,y):return ('seq',x,y)
    

    我只是将@decorator函数定义上的n_ary()装饰器替换为返回的包含的包装函数上的@functools.wraps()装饰器。