font - Cierres en Python
subplot title python (6)
¿Probablemente porque quieres obtener global mientras no es global? Por cierto, apply 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)
He estado tratando de aprender Python, y si bien estoy entusiasmado con el uso de cierres en Python, he estado teniendo problemas para hacer que algún 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, fáciles de leer y, en general, más "pitónicas" para implementar esto; sin embargo, mi objetivo es comprender exactamente cómo funcionan los cierres en Python, y cómo se diferencian de Lisp, por lo que no estoy interesado en soluciones alternativas, simplemente por qué mi código no funciona y qué puedo hacer (si es que hay algo) para corregirlo. eso.
El problema con el que me estoy fibm
es cuando trato de 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 "<stdin>", line 1, in <module>
File "memoize.py", line 14, in mfun
cache = get(args)
NameError: global name ''get'' is not defined
>>>
Al ver que soy nuevo en Python, no sé si he hecho algo mal, o si esto es solo una limitación del lenguaje. Espero que sea el primero. :-)
Desea poner global get
al comienzo de cada función (excepto get
).
el def get
es una asignación al nombre get
, por lo que quieres ser declarado global antes de eso.
Poner global get
en mfun y vset hace que funcionen. No puedo señalar las reglas de alcance que hacen que esto sea necesario, pero funciona ;-)
Tus opiniones son bastante lisas también ... :)
El problema está en su alcance, no en sus cierres. Si le apetece leer algo, entonces puede intentar 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 declaración global get
. global
refiere al alcance más externo, y como no hay ninguna función global get
, throws.
Lo que necesita es un especificador de acceso para las variables en el alcance adjunto, y no el alcance 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, oldget
referencias global get
y oldget
y funciona correctamente.
Get
no es global, sino local para la función circundante, por eso falla la declaración global
.
Si elimina el global
, aún falla, porque no puede asignarle el nombre de la variable capturada. Para solucionarlo, puede usar un objeto como la variable capturada por sus cierres y luego 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 forma, no es necesario que asigne los nombres de las variables capturadas, pero obtenga lo que desea.
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 local (get); sin embargo, también realicé algunas mejoras al tratar de mantener su *
tos *
rotura *
tos *
cierre. Por lo general, el caché es un dict en lugar de una lista vinculada de cierres.
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]