【问题标题】:two decorators on one function,the back one not work as expected?一个功能上有两个装饰器,后面的一个不能按预期工作?
【发布时间】:2016-12-22 08:28:23
【问题描述】:

我在一个函数上有两个装饰器。 每个装饰器都为函数添加一个属性,但第一个装饰器的属性在启动后并没有传递给函数。

为什么f3.calls在下面的代码中仍然为0?

def memorize(fn):
    result_list3 = {}

    @wraps(fn)
    def wrapper(n):
        wrapper.calls2 += 1
        print("memorize calls2", wrapper.calls2)
        found = result_list3.get(n)
        if found is not None:
            result = found
        else:
            result = fn(n)
            result_list3.update({n: result})
            # print(result_list3)
        return result

    wrapper.calls2 = 0
    return wrapper


def countt(fn):
    @wraps(fn)
    def wrapper(n):
        wrapper.calls += 1
        print("countt calls", wrapper.calls)
        result = fn(n)
        return result

    wrapper.calls = 0
    return wrapper


@memorize
@countt
def f3(n):
    if n < 3:
        return n
    else:
        result = f3(n - 1) + 2 * f3(n - 2) + 3 * f3(n - 3)
        return result


if __name__ == '__main__':
    print(f3(10))
    print(f3.calls)  # 0
    print(f3.calls2)  # 25

这是日志:

memorize calls2 1
countt calls 1
memorize calls2 2
countt calls 2
memorize calls2 3
countt calls 3
memorize calls2 4
countt calls 4
memorize calls2 5
countt calls 5
memorize calls2 6
countt calls 6
memorize calls2 7
countt calls 7
memorize calls2 8
countt calls 8
memorize calls2 9
countt calls 9
memorize calls2 10
countt calls 10
memorize calls2 11
countt calls 11
memorize calls2 12
memorize calls2 13
memorize calls2 14
memorize calls2 15
memorize calls2 16
memorize calls2 17
memorize calls2 18
memorize calls2 19
memorize calls2 20
memorize calls2 21
memorize calls2 22
memorize calls2 23
memorize calls2 24
memorize calls2 25
1892
0
25

【问题讨论】:

    标签: python decorator python-decorators


    【解决方案1】:

    每个装饰器都会为名称f3分配一个新函数wrapper,这意味着调用了3个不同的函数:原始f3、从countt返回的wrapperwrappermemorize 返回。在您的最后几行中,print(f3...) 指的是来自memorizewrapper。但是在countt 中的wrapper 中,wrapper.calls += 1 指的是来自counttwrapper,而不是来自memorize 的那个。所以你看不到wrapper.calls += 1 的效果,因为你看错了函数对象。但是,如果您检查 f3.__wrapped__.calls,您将看到正确的值 (11)。

    最终的f3(来自memorizewrapper)甚至有一个calls 属性的原因是因为memorize 中的@wraps 从@987654346 的wrapper 复制了该属性@从memorizewrapper

    【讨论】:

      【解决方案2】:

      您正在查看错误的calls 属性。这是发生了什么:

      • countt 产生一个 wrapper() 函数来替换修饰函数。我们称之为countt.&lt;local&gt;.wrapper()(即使functools.wraps()__name__ 属性设置为f3)。这包装了原始函数fn,并且functools.wraps() 将所有函数属性复制到countt.&lt;local&gt;.wrapper()。您向countt.&lt;local&gt;.wrapper() 添加了calls 属性,所以我们称之为countt.&lt;local&gt;.wrapper.calls

      • memorize 产生一个 wrapper() 函数来替换修饰函数 fn。我们称之为memorize.&lt;local&gt;.wrapper()。这包装了传入的函数fn,这里是countt.&lt;local&gt;.wrapper()functools.wraps() 将所有函数属性复制到memorize.&lt;local&gt;.wrapper()包括countt.&lt;local&gt;.wrapper.calls,变为memorize.&lt;local&gt;.wrapper.calls,设置为0。您向memorize.&lt;local&gt;.wrapper() 添加了calls2 属性,所以我们称之为memorize.&lt;local&gt;.wrapper.calls2

      然后运行代码。 countt.&lt;local&gt;.wrapper() 不断更新countt.&lt;local&gt;.wrapper.calls。请注意,memorize.&lt;local&gt;.wrapper.calls这里永远不会改变,这是一个不同的、独立的属性

      然后打印f3.callsf3.calls2f3.calls 是 0,因为那真的是 memorize.&lt;local&gt;.wrapper.calls,而不是 countt.&lt;local&gt;.wrapper.calls

      如果您运行的是 Python 3.2 或更高版本,则可以使用 __wrapped__ 属性访问包装函数; f3.__wrapped__ 这里是countt.&lt;local&gt;.wrapper(),所以你可以看到正确 calls 属性:

      print(f3.__wrapped__.calls)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-01-10
        • 1970-01-01
        • 2016-06-20
        • 1970-01-01
        • 1970-01-01
        • 2023-01-11
        • 1970-01-01
        相关资源
        最近更新 更多