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

2024-10-03 19:20:37 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

Tags: 函数returnifdefresultwrapperfnprint
2条回答

您看到了错误的calls属性。下面是发生的情况:

  • countt生成一个wrapper()函数来替换修饰函数。我们称之为countt.<local>.wrapper()(即使functools.wraps()__name__属性设置为f3)。这将包装原始函数fn,并且functools.wraps()将所有函数属性复制到countt.<local>.wrapper()。您向countt.<local>.wrapper()添加了一个calls属性,因此我们称之为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。您向memorize.<local>.wrapper()添加了一个calls2属性,因此我们称之为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。你知道吗

如果运行的是Python3.2或更新版本,则可以使用__wrapped__属性来访问wrapped函数;f3.__wrapped__这里是countt.<local>.wrapper(),因此可以使用以下属性查看正确的calls属性:

print(f3.__wrapped__.calls)

每个decorator为名称f3分配一个新函数wrapper,这意味着有3个不同的函数被调用:原始的f3,从countt返回的wrapper,以及从memorize返回的wrapper。在最后一行中,print(f3...)指的是来自memorizewrapper。但是在countt中的wrapper中,wrapper.calls += 1指的是来自counttwrapper,而不是来自memorize的。所以你看不到wrapper.calls += 1的效果,因为你看的是错误的函数对象。但是,如果您选中f3.__wrapped__.calls,您将看到正确的值(11)。你知道吗

最后的f3(来自memorizewrapper)甚至有一个calls属性的原因是memorize中的@wraps将该属性从wrappercountt复制到来自memorizewrapper。你知道吗

相关问题 更多 >