【问题标题】:Memoize decorators failing to memoize (when not using decorator syntax)Memoize 装饰器无法记忆(不使用装饰器语法时)
【发布时间】:2012-03-22 09:39:56
【问题描述】:

我有一个简单的 memoizer 装饰器:

def funcmemo(f):
    memo = {}
    @wraps(f)
    def wrapper(*args):
    if args in memo:
        return memo[args]
    else:
        temp = f(*args)
        print "memoizing: ", args, temp  
        memo[args] = temp
        return temp
    return wrapper

现在,当我通过“@”标记使用它时,

@funcmemo
def fib(n):
    print "fib called with:", n
    if n < 2: return n
    return fib(n-2) + fib(n-1)

res = fib(3)
print "result:", res

它可以正常工作,如打印输出所示:

fib called with: 3
fib called with: 1
memoizing:  (1,) 1
fib called with: 2
fib called with: 0
memoizing:  (0,) 0
memoizing:  (2,) 1
memoizing:  (3,) 2
result:  2

但是,当我这样做时:

def fib(n):
    print "fib called with:", n
    if n < 2: return n
    return fib(n-2) + fib(n-1)

memfib = funcmemo(fib)
res = memfib(3)
print "result:", res

显然调用了一个未修饰的 fib,只有最终的返回值“到达”缓存(显然会导致极大的减速):

fib called with: 3
fib called with: 1
fib called with: 2
fib called with: 0
fib called with: 1
memoizing:  (3,) 2
result: 2

奇怪的是,这个工作正常:

def fib(n):
    print "fib called with:", n
    if n < 2: return n
    return fib(n-2) + fib(n-1)

fib = funcmemo(fib)
res = fib(3)
print "result:", res

此外,基于类的版本也会发生同样的事情:

class Classmemo(object):
    def __init__ (self, f):
        self.f = f
        self.mem = {}
    def __call__ (self, *args):
        if args in self.mem:
            return self.mem[args]
        else:
            tmp = self.f(*args)
            print "memoizing: ", args, temp
            self.mem[args] = tmp
            return tmp

使用“匿名”修饰函数时也会出现问题,例如

res = Classmemo(fib)(3)

我很高兴了解这背后的原因。

【问题讨论】:

  • 是的,你是对的。但是你的问题在哪里?

标签: python decorator


【解决方案1】:

这没什么好奇怪的。当你这样做时

memofib = funcmemo(fib)

您不会以任何方式更改函数fib 指向的函数,而是创建一个新函数并将名称memofib 指向它。

所以当memofib 被调用时,它会调用名称fib 所指向的函数——它递归地调用自身,而不是memofib——所以不会发生记忆。

在你的第二个例子中,你这样做

fib = funcmemo(fib)

所以它递归地调用自己并且记忆发生在所有级别。

如果您不想像装饰器版本或第二个示例那样覆盖名称 fib,您可以更改 fib 以获取函数名称:

def fib(n, fibfunc):
    print "fib called with:", n
    if n < 2: return n
    return fibfunc(n-2, fibfunc) + fibfunc(n-1, fibfunc)

memofib = funcmemo(fib)
res = fib(3, memofib)

您也可以使用fixed point combinator 来避免每次都通过fibfunc

def Y(f):
    def Yf(*args):
        return f(Yf)(*args)
    return f(Yf)

@Y
def fib(f):
    def inner_fib(n):
        print "fib called with:", n
        if n < 2: return n
        return f(n-2) + f(n-1)
    return inner_fib

【讨论】:

  • 另外,感谢您通过提出定点组合器让我完成大脑崩溃的课程。
  • @AndrásKovács 我无法抗拒:)。当我输入传递函数的版本时,我意识到这可能是唯一一次我可以使用问题中没有特别提及的版本。
【解决方案2】:

如果您的问题只是一个简单的为什么,我想答案只是因为fib() 的递归调用确实调用了名称为fib() 的函数。要装饰你必须替换指针fib 的值; memfib = funcmemo(fib) 和类版本都没有这样做。

【讨论】:

    猜你喜欢
    • 2020-01-11
    • 2011-05-24
    • 2015-08-23
    • 2014-01-14
    • 2018-12-10
    • 2010-12-15
    • 2014-03-15
    • 1970-01-01
    • 2017-11-18
    相关资源
    最近更新 更多