【问题标题】:mechanism behind decoration in pythonpython中装饰背后的机制
【发布时间】:2018-03-04 23:48:49
【问题描述】:

下面是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由于update_wrapper(decorator(n_ary),n_ary)的功能而更新

  3. 第三部分应该是seq的更新,不过不明白update_wrapper这个函数什么时候用的。

【问题讨论】:

  • 请问您在哪里找到的?这是一个试图变得太聪明而没有收获的人,我很想看看他们的动机是什么。

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


【解决方案1】:

装饰只是调用另一个函数并用结果替换当前函数对象的语法糖。你试图理解的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)
    

    lambda 函数对象上设置正确的__name__ 等。

  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 函数返回的任何位置。

所以最后,所有这些舞蹈让你得到的是 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)

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-13
    • 2013-10-24
    • 1970-01-01
    相关资源
    最近更新 更多