Cierres en Python

He estado tratando de aprender Python, y aunque estoy entusiasmado con el uso de cierres en Python, he tenido problemas para hacer que un código funcione correctamente:

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) 

Básicamente, lo que se supone que debe hacer es usar cierres para mantener el estado memorizado de la función. Me doy cuenta de que probablemente haya muchas formas más rápidas, más fáciles de leer y, en general, más “Pythonic” para implementar esto; sin embargo, mi objective es entender exactamente cómo funcionan los cierres en Python y en qué se diferencian de Lisp, por lo que no me interesan las soluciones alternativas, solo por qué mi código no funciona y qué puedo hacer (si es que hago) para solucionarlo. eso.

El problema con el que me estoy fibm es cuando bash usar fibm : Python insiste en que get no está definido:

 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 >>> 

Viendo que soy nuevo en Python, no sé si he hecho algo mal o si esto es solo una limitación del idioma. Espero que sea la primera. 🙂

El problema está en su scope, no en sus cierres. Si está preparado para una lectura profunda, puede probar http://www.python.org/dev/peps/pep-3104/ .

Si ese no es el caso, aquí está la explicación simple:

El problema está en la statement global get . global refiere al ámbito más externo, y dado que no hay ninguna función global, se lanza.

Lo que necesita es un especificador de acceso para las variables en el ámbito adjunto, y no el scope global.

En Python 3.0, como he probado, la palabra clave nonlocal es exactamente lo que necesita, en lugar de global .

 nonlocal get ... 

En Python 2.x, acabo de eliminar las referencias global get y oldget y funciona correctamente.

 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" 

La salida es:

 fib(35) is 9227465 and took 29860703 calls fibm(35) is 9227465 and took 36 calls 

Similar a otras respuestas, sin embargo esta funciona. 🙂

El cambio importante del código en la pregunta es la asignación a un no local no global (obtener); sin embargo, también realicé algunas mejoras al tratar de mantener el uso del cierre * tos * roto * tos * . Por lo general, el caché es un dict en lugar de una lista vinculada de cierres.

Desea poner global get al principio de cada función (excepto get mismo).

la def get es una asignación al nombre que se get , por lo tanto, antes de eso, debes ser declarado global.

Poner global get en mfun y vset hace que funcionen. No puedo señalar las reglas de scope que hacen esto necesario, pero funciona 😉

Sus comentarios son bastante lispy también … 🙂

Get no es global, sino local para la función que lo rodea, por eso la statement global falla.

Si elimina el global , sigue fallando, porque no puede asignar el nombre de la variable capturada. Para solucionar este problema, puede usar un objeto como la variable capturada por sus cierres y simplemente cambiar las propiedades de ese objeto:

 class Memo(object): pass def memoize(fn): def defaultget(key): return (False,) memo = Memo() memo.get = defaultget def vset(key, value): oldget = memo.get def newget(ky): if key==ky: return (True, value) return oldget(ky) memo.get = newget def mfun(*args): cache = memo.get(args) if cache[0]: return cache[1] val = apply(fn, args) vset(args, val) return val return mfun 

De esta manera, no es necesario que asigne nombres de variables capturados, sino que obtenga lo que desea.

¿Probablemente porque quieres lo global pero no es global? Por cierto, la aplicación está en desuso, use fn (* args) en su lugar.

 def memoize(fn): def get(key): return (False,) def vset(key, value): def newget(ky): if key==ky: return (True, value) return get(ky) get = newget def mfun(*args): cache = get(args) if (cache[0]): return cache[1] val = 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) 

Creo que la mejor manera sería:

 class Memoized(object): def __init__(self,func): self.cache = {} self.func = func def __call__(self,*args): if args in self.cache: return cache[args] else: self.cache[args] = self.func(*args) return self.cache[args]