一个函数上有两个装饰器,后面一个没有按预期工作?

时间:2016-12-22 08:28:23

标签: python decorator python-decorators

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

为什么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

2 个答案:

答案 0 :(得分:1)

每个装饰器为名称wrapper分配一个新函数f3,这意味着有三个不同的函数被调用:原始f3wrapper从{返回} {1}}以及countt返回的wrapper。在最后一行中,memorize引用print(f3...)中的wrapper。但在memorize中的wrapper内,countt指的是来自wrapper.calls += 1的{​​{1}}而不是wrapper中的countt。因此,您没有看到memorize的效果,因为您正在查看错误的函数对象。但是,如果您选中wrapper.calls += 1,您将看到正确的值(11)。

最终f3.__wrapped__.calls(来自f3的{​​{1}})甚至具有wrapper属性的原因是因为memorize calls@wraps中的memorize属性从wrapper复制到countt

答案 1 :(得分:0)

您正在查看错误的calls属性。以下是发生的事情:

  • countt生成wrapper()函数来替换修饰函数。我们称之为countt.<local>.wrapper()(即使functools.wraps()__name__属性设置为f3)。这包装了原始函数fnfunctools.wraps()将所有函数属性复制到countt.<local>.wrapper()。您向calls添加了countt.<local>.wrapper()属性,因此我们称之为countt.<local>.wrapper.calls

  • memorize生成wrapper()函数来替换修饰函数fn。我们称之为memorize.<local>.wrapper()。这包含传入的函数fn,这里是countt.<local>.wrapper()functools.wraps()将所有功能属性复制到memorize.<local>.wrapper()包括countt.<local>.wrapper.calls ,成为memorize.<local>.wrapper.calls,设置为0。您向calls2添加了memorize.<local>.wrapper()属性,因此我们称之为memorize.<local>.wrapper.calls2

然后运行代码。 countt.<local>.wrapper()不断更新countt.<local>.wrapper.calls。请注意,memorize.<local>.wrapper.calls 从未在此处更改,这是不同的独立属性

然后打印f3.callsf3.calls2f3.calls为0,因为它真的是memorize.<local>.wrapper.calls,而不是countt.<local>.wrapper.calls

如果您运行的是Python 3.2或更高版本,则可以使用__wrapped__属性访问包装函数; f3.__wrapped__此处为countt.<local>.wrapper(),因此您可以看到正确的 calls属性:

print(f3.__wrapped__.calls)