I’ve been trying to learn Python, and while I’m enthusiastic about using closures in Python, I’ve been having trouble getting some code to work properly:
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)
Basically, what this is supposed to do is use closures to maintain the memoized state of the function. I realize there are probably many faster, easier to read, and in general more ‘Pythonic’ ways to implement this; however, my goal is to understand exactly how closures work in Python, and how they differ from Lisp, so I’m not interested in alternative solutions, just why my code doesn’t work and what I can do (if anything) to fix it.
The problem I’m running into is when I try to use fibm – Python insists that get isn’t defined:
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 '<stdin>', line 1, in <module> File 'memoize.py', line 14, in mfun cache = get(args) NameError: global name 'get' is not defined >>>
Seeing as I’m new to Python, I don’t know if I’ve done something wrong, or if this is just a limitation of the language. I’m hoping it’s the former. 🙂
The problem is in your scoping, not in your closures. If you’re up for some heavy reading, then you can try http://www.python.org/dev/peps/pep-3104/.
If that’s not the case, here’s the simple explanation:
The problem is in the statement
global get.globalrefers to the outermost scope, and since there isn’t any global functionget, it throws.What you need, is an access specifier for variables in the enclosing scope, and not the global scope.
In python 3.0, as I’ve tested, the
nonlocalkeyword is exactly what you need, in the place ofglobal.In python 2.x, I just removed the
global getand theoldgetreferences and it works properly.