我一直在努力学习Python,虽然我热衷于在Python中使用闭包,但我一直无法使代码正常工作:
def memoize(fn): def get(key): return (False,) def vset(key, value): global get oldget = get def newget(ky): if key==ky: return (True, value) return oldget(ky) get = newget def mfun(*args): cache = get(args) if (cache[0]): return cache[1] val = apply(fn, args) vset(args, val) return val return mfun def fib(x): if x<2: return x return fib(x-1)+fib(x-2) def fibm(x): if x<2: return x return fibm(x-1)+fibm(x-2) fibm = memoize(fibm)
基本上,这应该做的是使用闭包来维持函数的memoized状态.我意识到可能有更快,更容易阅读,并且通常更多'Pythonic'方式来实现这一点; 但是,我的目标是准确理解闭包在Python中是如何工作的,以及它们与Lisp的区别,所以我对替代解决方案不感兴趣,为什么我的代码不起作用以及我可以做什么(如果有的话)修复它.
我遇到的问题是当我尝试使用时fibm
- Python坚持get
没有定义:
Python 2.6.1 (r261:67515, Feb 1 2009, 11:39:55) [GCC 4.0.1 (Apple Inc. build 5488)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import memoize >>> memoize.fibm(35) Traceback (most recent call last): File "", line 1, in File "memoize.py", line 14, in mfun cache = get(args) NameError: global name 'get' is not defined >>>
看到我是Python的新手,我不知道我做错了什么,或者这只是语言的限制.我希望它是前者.:-)
问题在于您的范围,而不是您的关闭.如果你想读一些重读,那么你可以尝试http://www.python.org/dev/peps/pep-3104/.
如果不是这样,这里有一个简单的解释:
问题出在声明中global get
.global
它指的是最外层的范围,并且由于没有任何全局函数get
,它会抛出.
您需要的是封闭范围中变量的访问说明符,而不是全局范围.
在python 3.0中,正如我测试的那样,nonlocal
关键字正是你所需要的,而不是代替global
.
nonlocal get ...
在python 2.x中,我刚删除global get
了oldget
引用并且它正常工作.
def memoize(fn): get = [lambda key: (False, None)] def vset(args): value = fn(*args) oldget = get[0] def newget(key): if args == key: return (True, value) return oldget(key) get[0] = newget return value def mfun(*args): found, value = get[0](args) if found: return value return vset(args) return mfun CALLS = 0 def fib(x): global CALLS CALLS += 1 if x<2: return x return fib(x-1)+fib(x-2) @memoize def fibm(x): global CALLS CALLS += 1 if x<2: return x return fibm(x-1)+fibm(x-2) CALLS = 0 print "fib(35) is", fib(35), "and took", CALLS, "calls" CALLS = 0 print "fibm(35) is", fibm(35), "and took", CALLS, "calls"
输出是:
fib(35) is 9227465 and took 29860703 calls fibm(35) is 9227465 and took 36 calls
与其他答案类似,但这个有效.:)
问题中代码的重要变化是分配给非全局非本地(get); 不过,我也试图保持你做了一些改进*
咳*
破*
咳嗽*
封闭使用.通常,缓存是dict而不是闭包的链接列表.