双重包装斐波那契

Double wrapping Fibonacci

我尝试在递归 Fibonacci 函数上使用两个不同的包装器来:1) 计算递归次数,2) 记住计算值以减少所需的计算量。

由于每个包装函数都会在创建的函数上创建一个新的函数属性,再次包装后我无法再访问它。 有没有办法在不执行具有两种效果的单个包装函数的情况下仍然访问它?

def count(f):
    def f1(*args):
        f1.counter += 1
        return f(*args)
    f1.counter = 0
    return f1

def memoize(f):
    def f1(*args):
        if not args in f1.memo:
            f1.memo[args] = f(*args)
        return f1.memo[args]
    f1.memo = {}
    return f1

@memoize
@count
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

在此之后我可以访问 fib.memo 但不能访问 fib.counter,相反,如果我在计数之前用 memoize 包装 fib。

这个问题最初作为 How to make a chain of function decorators? 的副本关闭,其中正确地指出使用 functools.wraps 链接装饰器可以通过将包装函数的所有属性复制到包装器来提供帮助功能。

但是,我重新提出这个问题,因为使用 functools.wraps 不会完全适用于你的情况,因为你的 counter 属性是一个不可变的整数,所以 wraps 复制包装器的 counter 属性实际上只是将其初始值 0 复制到包装器,随后对包装函数的 counter 属性的更改不会反映在包装器的 counter 中属性,这就是为什么:

from functools import wraps

def count(f):
    @wraps(f)
    def f1(*args):
        f1.counter += 1
        return f(*args)
    f1.counter = 0
    return f1

def memoize(f):
    @wraps(f)
    def f1(*args):
        if not args in f1.memo:
            f1.memo[args] = f(*args)
        return f1.memo[args]
    f1.memo = {}
    return f1

@memoize
@count
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(4))
print(fib.memo)
print(fib.counter)

错误输出:

3
{(1,): 1, (0,): 0, (2,): 1, (3,): 2, (4,): 3}
0

要解决这个问题,您必须使用可变对象初始化 counter。由于 Python 本身没有可变整数,您可以创建一个自定义 class 来代替整数:

class Int:
    def __init__(self, value=0):
        self.value = value

def count(f):
    @wraps(f)
    def f1(*args):
        f1.counter.value += 1
        return f(*args)
    f1.counter = Int()
    return f1

这样:

print(fib(4))
print(fib.memo)
print(fib.counter.value)

会正确输出:

3
{(1,): 1, (0,): 0, (2,): 1, (3,): 2, (4,): 3}
5