2009-02-03 12 views
6

que he estado tratando de aprender Python, y aunque estoy entusiasmado con el uso de cierres en Python, he estado teniendo problemas para conseguir algo de código para que funcione correctamente:Cierres en 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) 

Básicamente, lo se supone que esto es hacer 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 que estoy corriendo en es cuando trato de usar fibm - Python insiste en que no está definido 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 "<stdin>", line 1, in <module> 
    File "memoize.py", line 14, in mfun 
    cache = get(args) 
NameError: global name 'get' is not defined 
>>> 

En vista de 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. :-)

Respuesta

7

El problema está en su alcance, no en sus cierres . Si le apetece leer algo pesado, 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 declaración global get. global se refiere al alcance más externo, y dado que no existe ninguna función global get, arroja.

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

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

nonlocal get 
... 

En Python 2.x, me acaba de quitar la global get y las referencias oldget y funciona correctamente.

+0

Entonces, es un defecto en el lenguaje. Esperaba que no fuera a decir eso. :-(Una posible solución es almacenar el valor en una estructura mutable (como una lista) y modificar eso, pero es bastante hackish. Quería una versión 2.X para compatibilidad, pero podría tener que dar el salto a 3000. –

+1

Hay una solución, bastante simple, a menos que se confunda con tu concepto de pureza: simplemente haz que get() sea una función global. Dicho esto, debo decir que tu método de memorización me parece demasiado complicado :) – sykora

+0

¿Cómo funciona correctamente? Solo elimina las líneas "global get" y "oldget = get" y nunca usa un valor guardado. –

0

¿Quieres poner global get al comienzo de cada función (excepto get sí mismo).

def get es una asignación para el nombre get, por lo que desea ser declarado global antes de eso.

Poner global get en mfun y vset hace que funcionen. No puedo señalar a las reglas de alcance que lo haga necesario, pero funciona ;-)

Sus conses son bastante lispy también ... :)

+0

Hmm .. en mi máquina, poniendo get global en get y vset no funcionó. Agregarlo al principio de memoize funcionó, pero eso hace que cada invocación de memoize use la misma función get (no es deseable). –

+0

ah sí. Nota para uno mismo: tomar café, despertar, * luego * responder ;-) –

0

Probablemente porque quiere global obtener mientras no es un global? Por cierto, aplicar 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) 
+0

Gracias por el consejo sobre aplicar. Sin embargo, ejecutar el código modificado anterior funciona en el sentido de que da la respuesta correcta, pero no utiliza la memoria en absoluto. (puede verificar esto ejecutando algo como fibm (35), que, si se ha memorizado, debería ser casi instantáneo. –

+0

) <- close paren ;-) –

+0

x = y asigna (o crea) la variable local x, a menos que hay una declaración global en algún lugar de esa función. –

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

de salida es:

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

Al igual que en otras respuestas, sin embargo funciona éste. :)

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 ** tos * tos cerrados uso. Por lo general, el caché es un dict en lugar de una lista vinculada de cierres.

+0

No es ideal, pero la solución funciona bastante bien, supongo. Gracias por pulir el código un poco también. –

+0

En mi humilde opinión, el uso de este código para cualquier cosa más que aprender sobre cómo Python maneja los cierres está condenado. Los otros cambios son más para empujarte en la dirección correcta que cualquier otra cosa. –

+0

Ese fue el objetivo principal. Si estuviera codificando en Python, usaría diccionarios en su lugar, pero aprecio pequeños detalles como establecer múltiples valores en una declaración y usar fn (* args) en lugar de aplicar. –

1

Get no es global, sino local para la función circundante, es por eso que la declaración global falla.

Si elimina el global, aún falla, porque no puede asignar el nombre de la variable capturada. Para evitar eso, se puede usar un objeto como la variable capturada por los cierres y que 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 asignar a los nombres de las variables capturadas, pero aún así obtener que querías.

0

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]