2010-12-30 11 views
20

Mam funkcję o nazwie runquery, która wykonuje wywołania do bazy danych, a następnie generuje wiersze, jeden po drugim. Napisałem dekorator memoize (a dokładniej, ukradłem go po prostu z this stackoverflow question), ale po kolejnych wywołaniach po prostu wydaje pustą sekwencję, prawdopodobnie dlatego, że wartości generatora można uzyskać tylko raz.Czy mogę zapamiętać generator Pythona?

Jak mogę zmodyfikować dekorator zapamiętywania, który działa dla generatorów Pythona? Zdaję sobie sprawę, że w pewnym momencie będę musiał go zapisać w pamięci, ale chciałbym poradzić sobie z tym w dekoratorze i nie modyfikować oryginalnej funkcji.

Obecny kod funkcji memoization jest:

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 

Odpowiedz

18

Zdaję sobie sprawę, jest to nieco starej pytanie, ale dla tych, którzy chcą pełne rozwiązanie: oto jeden, na podstawie sugestii jsbueno za:

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

Dzięki za ilustracje! Starałem się zrozumieć sposób użycia 'tee'. __But__ Wydaje mi się, że jest problem podczas sprawdzania instancji: powinieneś przetestować na 'collections.Iterable', ponieważ testowanie z' types.GeneratorType' działa tylko raz: podczas zwracania buforowanego iteratora (obiektu 'iterator.tee') na trzecim. wywołanie funkcji, pamięć podręczna zwróci wyczerpany iterator. –

+1

Masz rację! Jednak testowanie przeciwko 'collections.Iterable 'również byłoby błędne, ponieważ listy i łańcuchy itd. Są również iterable. Zmieniłem zestawy na '(GeneratorType, _tee)', więc działa również dla obiektów tee. – Robin

+1

Tak, to zdecydowanie przesada, ale w Pythonie 2.7 nie ma obiektu '_tee' w' itertools', a funkcja 'itertools.tee' zwraca obiekty' itertools.tee'. Oczywiście nie jest to ich klasa, więc testowanie z 'itertools.tee' nie ma sensu (a także nie działa), stąd moja propozycja z' collections.Iterable'. W jakiej wersji python testowałeś swój kod? –

4

Tak. Jest dekorator opublikowany here. Zwróć uwagę, że jak mówi plakat, tracisz część korzyści z leniwego oceniania.

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. Nie obsługuje on jednak dostępu do przeplatanego pamiętanego wyniku. –

+0

To jest w porządku, ale ma pole do poprawy. Make * argumentuje parametry dekoratora: znacznie bardziej użyteczny niż jeden obowiązkowy argument. I dodaj listę do pamięci podręcznej _ przed użyciem "for" - to zrobi miejsce na użycie iteratora przed zakończeniem pierwszego uruchomienia. – jsbueno

+0

To nie jest dobre dla nieskończonych generatorów. – dionyziz

10
from itertools import tee 

sequence, memoized_sequence = tee (sequence, 2) 

Gotowe.

Generatory są łatwiejsze, ponieważ standardowa biblioteka ma tę metodę "tee"!

+2

Czy możesz edytować odpowiedź, aby pokazać, jak zintegrować to z powyższą funkcją memoize? Chciałbym wpisać coś takiego jak @memoize_generator powyżej funkcji, która przyniosła sekwencję. – bryn

+0

nie ma potrzeby wstawiania 2 w 'tee',' n = 2' jest domyślne –

Powiązane problemy