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, he relies on some sketchy behavior when I actually add an attribute of the function itself to maintain state. In principle, this function will only work on the first call. After that, all bets are disabled.
If we want to soften this restriction, we can use a temporary namespace. The problem is that to get a temporary namespace, we need a unique instance of the class (or a class, but the instance is cleaner and requires only 1 additional set of brackets). For this to happen on one line, we need to create a new built-in function and use it as the 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 broke it into several lines, for ease of reading, but it is a 1-liner at heart.
Version without import
(and the most reliable, but I think).
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 save state. Inside the function, we create a tuple. The first element is what we really want, the second element is the return value of the setattr function, which is used to update the state. To get rid of itertools.chain , we set the initial value in the namespace to x , so the class is already initialized to have the initial state. The second trick is that we use two forms of the iter argument to get rid of it.count() , which was used only to create an infinite iterative earlier. iter continues to call the function that 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 effectively created an infinite itertools without itertools or yield ! Think about it, I believe this latest version is the most reliable. Previous versions had an error in which they relied on the veracity of the return value f . I think they might have failed if f returned 0 . This latest version fixes this error.