2010-12-30 15 views
20

Tengo una función llamada runquery que hace llamadas a una base de datos y luego cede las filas, una por una. Escribí un decorador de memoise (o más exactamente, acabo de robar uno de this stackoverflow question), pero en las llamadas siguientes simplemente produce una secuencia vacía, presumiblemente porque los valores de un generador solo pueden obtenerse una vez.¿Puedo memorizar un generador de Python?

¿Cómo podría modificar el decorador de memoria que funciona para los generadores Python? Me doy cuenta de que tendré que almacenarlo en la memoria en algún momento, pero me gustaría manejar esto dentro del decorador y no modificar la función original.

El código actual de la función memoization es:

def memoized(f): 
    # Warning: Doesn't work if f yields values 
    cache={} 
    def ret(*args): 
     if args in cache: 
      return cache[args] 
     else: 
      answer=f(*args) 
      cache[args]=answer 
      return answer 
    return ret 

Respuesta

18

me di cuenta que es algo así como una vieja pregunta, pero para aquellos que quieren una solución completa: aquí está uno, basado en la sugerencia de jsbueno:

from itertools import tee 
from types import GeneratorType 

Tee = tee([], 1)[0].__class__ 

def memoized(f): 
    cache={} 
    def ret(*args): 
     if args not in cache: 
      cache[args]=f(*args) 
     if isinstance(cache[args], (GeneratorType, Tee)): 
      # the original can't be used any more, 
      # so we need to change the cache as well 
      cache[args], r = tee(cache[args]) 
      return r 
     return cache[args] 
    return ret 
+1

¡Gracias por su ilustración! Llevé años para entender la forma de usar 'tee'. __Pero__ creo que hay un problema al verificar la instancia: debes probar contra 'collections.Iterable', como prueba contra' types.GeneratorType' solo funciona una vez: al devolver el iterador en caché (objeto 'iterator.tee') al tercero llamar a la función, la caché devolverá un iterador agotado. –

+1

¡Tienes razón! Sin embargo, las pruebas en contra de 'collections.Iterable' también serían erróneas, ya que las listas, las cadenas, etc. son también iterables. Cambié los conjuntos a '(GeneratorType, _tee)', por lo que también funciona para los objetos de tee. – Robin

+1

Sí, definitivamente es un poco exagerado, pero, en Python 2.7, no hay ningún objeto '_tee' en' itertools', y la función 'itertools.tee' devuelve objetos' itertools.tee'. Obviamente, esta no es su clase, por lo que probar contra 'itertools.tee' no tiene sentido (y tampoco funciona), de ahí mi propuesta con' collections.Iterable'. ¿Bajo qué versión de Python has probado tu código? –

4

Sí. Hay un decorador publicado here. Tenga en cuenta que, como dice el afiche, usted pierde parte del beneficio de la evaluación perezosa.

def memoize(func): 
    def inner(arg): 
     if isinstance(arg, list): 
      # Make arg immutable 
      arg = tuple(arg) 
     if arg in inner.cache: 
      print "Using cache for %s" % repr(arg) 
      for i in inner.cache[arg]: 
       yield i 
     else: 
      print "Building new for %s" % repr(arg) 
      temp = [] 
      for i in func(arg): 
       temp.append(i) 
       yield i 
      inner.cache[arg] = temp 
    inner.cache = {} 
    return inner 


@memoize 
def gen(x): 
    if not x: 
     yield 0 
     return 

    for i in xrange(len(x)): 
     for a in gen(x[i + 1:]): 
      yield a + x[0] 


print "Round 1" 
for a in gen([2, 3, 4, 5]): 
    print a 

print 
print "Round 2" 
for a in gen([2, 3, 4, 5]): 
    print a 
+0

+1. Sin embargo, no admite el acceso entrelazado al resultado memoializado. –

+0

Esto está bien, pero tiene margen de mejora. Make * args the decorator parameters: mucho más útil que un argumento obligatorio. Y agregue la lista al caché antes del "para" - esto hará espacio para usar el iterador antes de que se complete la primera ejecución. – jsbueno

+0

Esto no es bueno para generadores infinitos. – dionyziz

10
from itertools import tee 

sequence, memoized_sequence = tee (sequence, 2) 

Listo.

¡Es más fácil para generadores porque la lib estándar tiene este método "tee"!

+2

¿Podría editar su respuesta para mostrar cómo integrar esto con la función de memoria arriba? Me gustaría escribir algo como @memoize_generator sobre una función que produjo una secuencia. – bryn

+0

sin necesidad de poner 2 en 'tee',' n = 2' es predeterminado –

Cuestiones relacionadas