I think this works:
import itertools as it
def g(f, x):
return it.chain([x],(setattr(g, 'x', f(getattr(g, 'x', x))) or getattr(g, 'x') for _ in it.count()))
def f(x):
return x + 1
gen = g(f, 1)
print next(gen)
print next(gen)
print next(gen)
print next(gen)
Of course, it relys on some sketchy behavior where I actually add an attribute to the function itself to keep the state. Basically, this function will only work the first time you call it. After that, all bets are off.
If we want to relax that restriction, we can use a temporary namespace. The problem is that to get a temporary namespace we need a unique class instance (or class, but an instance is cleaner and only requires 1 extra set of parenthesis). To make that happen in one line, we need to create a new function inline and use that as a default argument:
import itertools as it
def g(f, x):
return (lambda f, x, ns=type('foo', (object,), {})(): \
it.chain([x],
(setattr(ns, 'x', f(getattr(ns, 'x', x))) or getattr(ns, 'x')
for _ in it.count()))
)(f, x)
def f(x):
return x + 1
gen = g(f, 1)
print next(gen) == 1
print next(gen) == 2
print next(gen) == 3
print next(gen) == 4
print "first worked?"
gen2 = g(f, 2)
print next(gen2) == 2
print next(gen2) == 3
print next(gen2) == 4
I've broken it into a few lines, for readability, but it's a 1-liner at heart.
A version without any imports
(and the most robust one yet I believe).
def g(f, x):
return iter(lambda f=f, x=x, ns=type('foo', (object,), {'x':x}): ((getattr(ns, 'x'),setattr(ns, 'x', f(getattr(ns, 'x'))))[0]), object())
One trick here is the same as before. We create a lambda function with a mutable default argument to keep the state. Inside the function, we build a tuple. The first item is what we actually want, the second item is the return value of the setattr
function which is used to update the state. In order to get rid of the itertools.chain
, we set the initial value on the namespace to the value of x
so the class is already initialzed to have the starting state. The second trick is that we use the two argument form of iter
to get rid of it.count()
which was only used to create an infinite iterable before. iter
keeps calling the function you give it as the first argument until the return value is equal to the second argument. However, since my second argument is an instance of object
, nothing returned from our function will ever be equal to it so we've effectively created an infinite iterable without itertools
or yield
! Come to think of it, I believe this last version is the most robust too. Previous versions had a bug where they relied on the truthfulness of the return value of f
. I think they might have failed if f
returned 0
. This last version fixes that bug.