我有一个简单的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)
我很高兴能够了解这背后的原因。
答案 0 :(得分:5)
没有什么好奇的。当你这样做
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
答案 1 :(得分:2)
如果您的问题只是一个简单的为什么,我想答案只是因为fib()
的递归调用会调用名为fib()
的函数。要装饰你必须替换指针fib
的值;这不是由memfib = funcmemo(fib)
或类版本完成的。