kjo
kjo

Reputation: 35331

Function for calling a function repeatedly?

Consider the hypothetical function repeatcall, that takes as arguments a no-args callable func and a positive integer n, and returns a list whose members are obtained by executing func() n times. It supports an infinite stream of silly hijinks like:

>>> repeatcall(lambda: id(dict()), 5)
[45789920, 45788064, 45807216, 45634816, 45798640]

>>> urandom = lambda: struct.unpack('Q', open('/dev/urandom').read(8))[0]
>>> repeatcall(urandom, 3)
[3199039843823449742, 14990726001693341311L, 11583468019313082272L]

>>> class Counter(itertools.count): __call__ = itertools.count.next
>>> repeatcall(Counter(100, -2), 4)
[100, 98, 96, 94]

I could swear that I've seen a function like repeatcall somewhere in the Python 2.x standard libraries, but I can't find it. If I didn't dream this, where in the standard library can I find it?

PS: I know it's trivial to roll one's own, but I hate to reinvent wheels, especially those are already in the standard library. I am not asking how to roll my own.

Edit: made it even more explicit that I am not asking how to code repeatcall.

Upvotes: 10

Views: 6047

Answers (5)

NivPgir
NivPgir

Reputation: 133

While reading comments here I realized that iter(foo, expr) will create an infinite iterator of the result of foo as long as it never equals expr.

for the example in the question, iter(lambda: id(dict()), None) will create an infinite iterator of ids of dicts.

While not a direct answer to this question, I found it useful since in my usecase I wanted it to be part of a finite zip expression anyways and just needed the iterator to keep producing elements.

hopefully this might help others that stumble upon this question

Upvotes: 1

Marcin
Marcin

Reputation: 49886

There's a reason this doesn't exist: the idiomatic way to code a function that doesn't take arguments on each invocation, and returns something new is to code it as a generator.

You would then use a list comprehension or generator expression to call it as many times as you like: [next(gen) for i in xrange(5)]. Better yet, gen can itself be the result of a generator expression like (id(dict()) for i in (itertools.repeat(None))).

Thus, python has no library support for this because it supports it syntactically.

Upvotes: 4

Abhijit
Abhijit

Reputation: 63777

You can use the apply built-in function for this purpose

>>> def repeatcall(func,n):
    [apply(func) for i in range(0,n)]

>>> repeatcall(lambda: id(dict()), 5)
[56422096, 56422240, 56447024, 56447168, 56447312]

>>> import itertools
>>> class Counter(itertools.count): __call__ = itertools.count.next

>>> repeatcall(Counter(100, -2), 4)
[100, 98, 96, 94]
>>> 

Note** From the manual The use of apply() is equivalent to function(*args, **keywords).

So repeatcall can also be written as

>>> def repeatcall(func,n):
    [func() for i in range(0,n)]

Upvotes: 2

agf
agf

Reputation: 176970

You've seen this in the standard library docs, not the standard library itself.

It's repeatfunc from the itertools recipes:

def repeatfunc(func, times=None, *args):
    """Repeat calls to func with specified arguments.

    Example:  repeatfunc(random.random)
    """
    if times is None:
        return starmap(func, repeat(args))
    return starmap(func, repeat(args, times))

It allows arguments and should (theoretically) perform better than a list comprehension because func only has to be looked up once. repeat is also faster than range for when you're not actually using the counter.

Upvotes: 17

Rian Rizvi
Rian Rizvi

Reputation: 10845

Do you mean something like this?:

>> from random import random
>> print [random() for x in range(5)]
[0.015015074309405185,
 0.7877023608913573,
 0.2940706206824023,
 0.7140457069245207,
 0.07868376815555878]

Seems succinct enough no?

Upvotes: 3

Related Questions