Can I Memoize A Python Generator?
I have a function called runquery that makes calls to a database and then yields the rows, one by one. I wrote a memoize decorator (or more accurately, I just stole one from this s
Solution 1:
I realise this is somewhat of an old question, but for those who want a full solution: here's one, based on jsbueno's suggestion:
from itertools import tee
from types import GeneratorType
Tee = tee([], 1)[0].__class__
defmemoized(f):
cache={}
defret(*args):
if args notin cache:
cache[args]=f(*args)
ifisinstance(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
Solution 2:
from itertools import tee
sequence, memoized_sequence = tee (sequence, 2)
Done.
It is easier for generators because the standard lib has this "tee" method!
Solution 3:
Yes. There's a decorator posted here. Take note that as the poster says, you lose some of the benefit of lazy evaluation.
defmemoize(func):
definner(arg):
ifisinstance(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
@memoizedefgen(x):
ifnot x:
yield0returnfor 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
printprint"Round 2"for a in gen([2, 3, 4, 5]):
print a
Solution 4:
Similar to the other answers, but simpler if you know that f
is a generator:
defmemoized_generator(f):
cache = {}
@functools.wraps(f)defwrapper(*args, **kwargs):
k = args, frozenset(kwargs.items())
it = cache[k] if k in cache else f(*args, **kwargs)
cache[k], result = itertools.tee(it)
return result
return wrapper
Post a Comment for "Can I Memoize A Python Generator?"