重复调用函数的函数?

10
考虑一个假设函数repeatcall,它以no-args可调用对象func和正整数n作为参数,并返回一个列表,其中的成员是通过执行func()n次获得的。它支持无限多个傻瓜式把戏,比如:
>>> 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]

我记得在Python 2.x标准库中有一个名为repeatcall的函数,但我找不到它。如果这不是我的幻觉,那么请问在标准库中的哪里可以找到它?
注:我知道自己编写一个函数很简单,但我不想重复造轮子,尤其是已经有现成的函数在标准库中。我不是在问如何编写repeatcall函数。
编辑:更加明确地表明我并不是在询问如何编写repeatcall函数。

1
这不就是在一个范围内进行映射吗? - gbulmer
5个回答

17

你在标准库文档中看到了这个,而不是在标准库本身中。

它是来自itertools recipesrepeatfunc

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))

它允许传递参数且(理论上)比列表推导式执行得更好,因为只需要查找一次func。当你不需要使用计数器时repeat也比range更快。


1
从技术上讲,你的回答也不完全正确,除非你是在说它不存在于stdlib中。我认为使用itertools.starmap的配方与自己编写的配方没有什么区别。虽然我想通过展示这个例子,你间接地表达了必须自己编写的意思。 - jdi
3
是的,我想说的是它在标准库中不存在。他记得见过它,因为它在文档中。这就是他问题的答案。 - agf
1
那么我想,如果他不想看代码的任何实现,你所写的90%内容都是不必要的。因此,这90%就像其他内容一样。 - jdi
3
@jdi这里包含了90%的内容只是为了让人们不必点击“itertools”文档就能理解我在说什么。Stack Overflow的答案应尽可能独立于外部资源,以便更好地自给自足。 - agf
@Mr_and_Mrs_D 如果您确实需要包含所有结果的列表,那么可以使用itertools的一个重要优势是,如果您不需要一次性获取所有结果,可以逐个获取。 - agf
显示剩余4条评论

4
这是原因:编写一个无需在每次调用时传递参数且返回新内容的函数的惯用方式是将其编写为生成器。
然后,您可以使用列表推导式或生成器表达式多次调用它:[next(gen) for i in xrange(5)]。更好的方法是,gen 本身可以是生成器表达式的结果,例如 (id(dict()) for i in (itertools.repeat(None)))
因此,Python 没有对此提供库支持,因为它在语法上已经支持了这个功能。

2
@agf 不完全是这样。这需要至少两段文字来解释为什么没有符合 OP 要求的解决方案。 - Marcin
1
如果你感觉那么强烈,就举报它。为什么你对其他发表答案的人这么生气? - Marcin
其中没有“愤怒”的情绪。当一个回答与问题无关时,它就是噪音。当我犯了错误并发布了一些不相关的内容时,我会将其删除,并且我很感激别人指出我的错误。除非一个答案完全没有信息,否则我不会标记它;删除或不删除的决定由发布者做出,详见我在元社区上的提问。而且,如果一个回答的意图是提供有用的信息,而且信息并没有错,我也不会给它点踩。 - agf
显然,你有些愤怒,因为你刚才给我的(正确的)答案投了反对票。我刚看到这个回答和评论 - 我很惊讶你会在发生争议后贬低一个正确的答案,因为那个评论很清楚地表明你认为那是错的。 - agf
但是,对于for循环呢?我正在寻找相同的答案,虽然生成器或列表推导式都不错,但“for n in (func() for i in range(10))”似乎有点笨拙。 - Lucretiel
显示剩余4条评论

3
你是指这样的内容吗?
>> from random import random
>> print [random() for x in range(5)]
[0.015015074309405185,
 0.7877023608913573,
 0.2940706206824023,
 0.7140457069245207,
 0.07868376815555878]

似乎足够简洁了吧?

3
我知道自己制作一个东西很琐碎,但我不想重复造轮子。-- 这并没有回答问题。 - agf

2
您可以使用内置函数apply来实现此目的。
>>> 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]
>>> 

注意** 来自手册 使用apply()等同于function(*args, **keywords)。

因此,repeatcall也可以写成

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

1
apply自Python2.3起已被弃用。您可以直接使用*args和**kwargs调用函数。 - jdi
函数可以直接调用,即使它的名称作为参数传入:def repeatcall(func,n): return [func() for i in range(0,n)] - Rian Rizvi
是的,apply在这里和其他地方都是完全不必要的。 - Jochen Ritzel
2
这并没有回答问题——“我知道自己编写一个很简单,但我不想重复造轮子”。他不想要一个实现。 - agf
@agf:原问题发布后15分钟才添加了附言 :-) - Abhijit
1
不,它不是。看一下编辑历史记录; 它在原始版本中,只是没有加粗。 - agf

1

在阅读这里的评论时,我意识到iter(foo, expr)将创建一个无限迭代器,其结果为foo,只要它从未等于expr

对于问题中的示例,iter(lambda: id(dict()), None)将创建一个字典id的无限迭代器。

虽然不是直接回答这个问题,但我发现它很有用,因为在我的用例中,我希望它成为有限zip表达式的一部分,并且只需要迭代器继续产生元素。

希望这可以帮助其他遇到这个问题的人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接