2010-12-30 20 views
20

Ho una funzione chiamata runquery che effettua chiamate a un database e quindi restituisce le righe, una alla volta. Ho scritto un decoratore memoize (o più esattamente, ne ho appena rubato uno da this stackoverflow question) ma nelle chiamate successive si ottiene solo una sequenza vuota, presumibilmente perché i valori di un generatore possono essere restituiti solo una volta.Posso memoizzare un generatore Python?

Come è possibile modificare il decoratore di memoizzazione che funziona con i generatori Python? Mi rendo conto che a un certo punto dovrò memorizzarlo in memoria, ma mi piacerebbe gestirlo nel decoratore e non modificare la funzione originale.

Il codice attuale della funzione Memoizzazione è:

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 

risposta

18

Mi rendo conto che è un po 'di una vecchia questione, ma per coloro che desiderano una soluzione completa: ecco uno, basato su suggerimento di 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

Grazie per la tua illustrazione! Ho impiegato anni per capire come usare 'tee'. __But__ Penso che ci sia un problema durante il controllo dell'istanza: dovresti provare contro 'collections.Iterable', dato che il test contro' types.GeneratorType' funziona solo una volta: quando si restituisce l'iteratore memorizzato nella cache (oggetto 'iterator.tee') al terzo chiamata alla funzione, la cache restituirà un iteratore esaurito. –

+1

Hai ragione! Tuttavia, anche il test contro 'collections.Iterable' sarebbe sbagliato, dal momento che liste e stringhe ecc. Sono anch'esse iterabili. Ho cambiato i set in '(GeneratorType, _tee)', quindi funziona anche per gli oggetti tee. – Robin

+1

Sì, è sicuramente un po 'eccessivo, ma, in Python 2.7, non c'è alcun oggetto '_tee' in' itertools', e la funzione 'itertools.tee' restituisce oggetti' itertools.tee'. Ovviamente, questa non è la loro classe, quindi il test contro 'itertools.tee' non ha senso (e inoltre non funziona), quindi la mia proposta con' collections.Iterable'. Sotto quale versione di Python hai testato il tuo codice? –

4

Sì. C'è un decoratore pubblicato here. Prendi nota che, come dice il manifesto, perdi parte del beneficio della valutazione pigra.

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. Tuttavia, non supporta l'accesso interleaving al risultato memorizzato. –

+0

Questo va bene, ma ha margini di miglioramento. Rende * args i parametri del decoratore: molto più utile di un argomento obbligatorio. E aggiungi la lista alla cache _prima_ il "per" - questo farà spazio per usare l'iteratore prima che la prima esecuzione sia completata. – jsbueno

+0

Questo non va bene per i generatori infiniti. – dionyziz

10
from itertools import tee 

sequence, memoized_sequence = tee (sequence, 2) 

Fatto.

È più facile per i generatori perché la lib standard ha questo metodo "tee"!

+2

Potresti modificare la tua risposta per mostrare come integrarla con la funzione Memoize qui sopra? Mi piacerebbe digitare qualcosa come @memoize_generator sopra una funzione che ha prodotto una sequenza. – bryn

+0

non è necessario inserire 2 in 'tee',' n = 2' è il default –

Problemi correlati