13

What is an idiomatic way to create an infinite iterator from a function? For example

from itertools import islice
import random
rand_characters = to_iterator( random.randint(0,256) )
print ' '.join( islice( rand_characters, 100))

would produce 100 random numbers

Community
  • 1
  • 1
Jakub M.
  • 32,471
  • 48
  • 110
  • 179

2 Answers2

17

You want an iterator which continuously yields values until you stop asking it for new ones? Simply use

it = iter(function, sentinel)

which calls function() for each iteration step until the result == sentinel.

So choose a sentinel which can never be returned by your wanted function, such as None, in your case.

rand_iter = lambda start, end: iter(random.randint(start, end), None)
rand_bytes = rand_iter(0, 256)

If you want to monitor some state on your machine, you could do

iter_mystate = iter(getstate, None)

which, in turn, infinitely calls getstate() for each iteration step.

But beware of functions returning None as a valid value! In this case, you should choose a sentinel which is guaranteed to be unique, maybe an object created for exactly this job:

iter_mystate = iter(getstate, object())
mgilson
  • 300,191
  • 65
  • 633
  • 696
glglgl
  • 89,107
  • 13
  • 149
  • 217
  • 2
    The easiest sentinel to use is probably just `object()` – Jon Clements Nov 30 '12 at 14:21
  • @JonClements You are right; I just intended to avoid creating unneeded objects. But if you need it because your function legitimately returns `None`, you should indeed use `object()`. – glglgl Nov 30 '12 at 14:29
8

Every time I see iter with 2 arguments, I need to scratch my head an look up the documentation to figure out exactly what is going on. Simply because of that, I would probably roll my own:

def call_forever(callback):
    while True:
        yield callback()

Or, as stated in the comments by Jon Clements, you could use the itertools.repeatfunc recipe which allows you to pass arguments to the function as well:

import itertools as it
def repeatfunc(func, times=None, *args):
    """
    Repeat calls to func with specified arguments.
    Example:  repeatfunc(random.random)
    """
    if times is None:
        return it.starmap(func, it.repeat(args))
    return it.starmap(func, it.repeat(args, times))

Although I think that the function signature def repeatfunc(func,times=None,*args) is a little awkward. I'd prefer to pass a tuple as args (it seems more explicit to me, and "explicit is better than implicit"):

import itertools as it
def repeatfunc(func, args=(),times=None):
    """
    Repeat calls to func with specified arguments.
    Example:  repeatfunc(random.random)
    """
    if times is None:
        return it.starmap(func, it.repeat(args))
    return it.starmap(func, it.repeat(args, times))

which allows it to be called like:

repeatfunc(func,(arg1,arg2,...,argN),times=4) #repeat 4 times
repeatfunc(func,(arg1,arg2,...))                #repeat infinitely

instead of the vanilla version from itertools:

repeatfunc(func,4,arg1,arg2,...)    #repeat 4 times
repeatfunc(func,None,arg1,arg2,...) #repeat infinitely
mgilson
  • 300,191
  • 65
  • 633
  • 696