Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python: iterator from a function

Tags:

python

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

like image 724
Jakub M. Avatar asked Nov 30 '12 14:11

Jakub M.


2 Answers

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())
like image 75
glglgl Avatar answered Oct 24 '22 11:10

glglgl


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
like image 27
mgilson Avatar answered Oct 24 '22 09:10

mgilson