【问题标题】:What is the purpose of Python's itertools.repeat?Python 的 itertools.repeat 的目的是什么?
【发布时间】:2012-02-21 23:16:30
【问题描述】:

对于我能想到的 Python 的 itertools.repeat() 类的每一次使用,我都能想到另一个同样(可能更多)可接受的解决方案来达到同样的效果。例如:

>>> [i for i in itertools.repeat('example', 5)]
['example', 'example', 'example', 'example', 'example']
>>> ['example'] * 5
['example', 'example', 'example', 'example', 'example']

>>> list(map(str.upper, itertools.repeat('example', 5)))
['EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE']
>>> ['example'.upper()] * 5
['EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE']

在任何情况下itertools.repeat() 是最合适的解决方案吗?如果有,在什么情况下?

【问题讨论】:

  • 我添加了一个新答案,显示了 itertools 重复的原始激励用例。此外,我刚刚更新了 Python 文档以反映此使用说明。
  • 您的 4 个代码示例中有 3 个实际上不起作用。第一个创建生成器表达式,而不是 tuple(你想要 tuple(itertools.repeat('example', 5))),第二个将 'example' 本身相乘以生成 'exampleexampleexampleexampleexample',因为 ('example') 在第一个中不会生成 tuple放置(您需要('example',) * 5),而您的第三个示例使用map,它将返回一个map 对象,因为Python 3 map 是惰性的(您必须将其包装在list 中才能获得提供的结果)。这是一个有趣的问题,但伪造你的代码示例会伤害它。
  • @ShadowRanger,当我发表这篇文章时,我对 Python 还很陌生,我只是快速输入了一些示例,而没有检查实际输出。有点迂腐,但我现在已经修好了。谢谢! :)

标签: python python-3.x itertools


【解决方案1】:

itertools.repeat 的主要目的是提供用于 mapzip 的常量值流:

>>> list(map(pow, range(10), repeat(2)))     # list of squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

第二个目的是它提供了一种非常快速的方法来循环固定次数,如下所示:

for _ in itertools.repeat(None, 10000):
    do_something()

这比:

for i in range(10000):
    do_something().

前者获胜,因为它所要做的就是更新现有 None 对象的引用计数。后者失败了,因为 range()xrange() 需要制造 10,000 个不同的整数对象。

注意,Guido 自己在timeit() 模块中使用了这种快速循环技术。请参阅https://hg.python.org/cpython/file/2.7/Lib/timeit.py#l195 的来源:

    if itertools:
        it = itertools.repeat(None, number)
    else:
        it = [None] * number
    gcold = gc.isenabled()
    gc.disable()
    try:
        timing = self.inner(it, self.timer)

【讨论】:

  • 这个答案和repeat是个宝。为什么这隐藏在itertools 中而不是内置的? for _ in range(x): do() 就是这样一个常见的模式。
  • @Darkonaut 您的隐含假设是 Python 被设计为速度很快。不是。它旨在易于阅读。
  • @Veky 您似乎将 Python 语言与它的一种实现(CPython)混淆了。 Raymond 提到的参考计数不是该语言的一部分。语言本身没有速度的概念。
  • 我不明白你为什么会这样想。但我可以向你保证,当 Guido 设计 Python 时,他设计了一个非常具体的实现。 (是的,CPython。几乎 10 年后出现了其他实现。)在那种语言中,Pythonic 循环 n 次的方法是使用范围,而不是重复。即使 range 返回一个列表,它也是首选(xrange 后来出现)。因此,显然速度不是主要问题。我根本没有提到引用计数。
  • @Veky 我的假设不是“Python 的设计初衷是为了快速”,也不一定只是为了提供比for _ in range(x): do() 更好的习语,因为我没有关心序列,但是有一些 fast_er_ 用于紧密循环会很好。我最初的评论是关于解释器和库的,你的评论是关于语言设计的,并且听起来好像使用 range() 以外的任何东西都会以某种方式牺牲可读性来提高速度。 Python 的可读性主要源于语法,而不是解释器附带的函数或它们的实现方式。
【解决方案2】:

itertools.repeat 函数是惰性的;它只使用一项所需的内存。另一方面,(a,) * n[a] * n 习惯用法在内存中创建对象的 n 个副本。对于五个项目,乘法习语可能更好,但如果您必须重复某件事,例如一百万次,您可能会注意到资源问题。

不过,很难想象itertools.repeat 有很多静态用途。然而,itertools.repeat 是一个函数这一事实允许您在许多函数式应用程序中使用它。例如,您可能有一些库函数func,它对输入的可迭代进行操作。有时,您可能已经预先构建了各种项目的列表。其他时候,您可能只想对统一列表进行操作。如果列表很大,itertools.repeat 将节省您的内存。

最后,repeat 使itertools 文档中描述的所谓“迭代器代数”成为可能。甚至itertools 模块本身也使用repeat 函数。例如,以下代码作为itertools.izip_longest 的等效实现给出(即使实际代码可能是用C 编写的)。注意使用repeat 底部七行:

class ZipExhausted(Exception):
    pass

def izip_longest(*args, **kwds):
    # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
    fillvalue = kwds.get('fillvalue')
    counter = [len(args) - 1]
    def sentinel():
        if not counter[0]:
            raise ZipExhausted
        counter[0] -= 1
        yield fillvalue
    fillers = repeat(fillvalue)
    iterators = [chain(it, sentinel(), fillers) for it in args]
    try:
        while iterators:
            yield tuple(map(next, iterators))
    except ZipExhausted:
        pass

【讨论】:

  • 小问题:[a] * n 不会在内存中创建 n 个 a 副本。它创建对 a 的单个副本的 n 个引用。在某些情况下,差异可能非常显着;试试a = [[]] * 5; a[0].append(1)
  • 好点。我一直忘记 Python 中的几乎所有内容都是参考。我想这也可以在一定程度上减轻内存使用问题,但我猜一百万个引用仍然有不平凡的资源需求。
【解决方案3】:

您的foo * 5 示例表面上与itertools.repeat(foo, 5) 相似,但实际上完全不同。

如果你写 foo * 100000,解释器必须创建 100,000 个 foo 的副本才能给你答案。因此,这是一个非常昂贵且对内存不友好的操作。

但是如果你写itertools.repeat(foo, 100000),解释器可以返回一个提供相同功能的迭代器,并且在你需要它之​​前不需要计算结果——比如说,通过使用它在一个想要知道序列中每个结果的函数中。

这是迭代器的主要优势:它们可以推迟计算列表的一部分(或全部),直到您真正需要答案为止。

【讨论】:

  • 为什么不直接使用for i in range(100000):,然后在循环中访问foo,而不是询问这个函数你给了它什么值?
  • @TylerCrompton:迭代器可以传递给期望任何类型的迭代器的其他事物,而不考虑其内部内容。你不能对范围做同样的事情(它是可迭代的,但它本身不是一个迭代器)。
  • 我明白你的意思,但就你评论的结尾而言,在 Python 3 中?
  • range 在 Python 3 中是一个迭代器,但在 Python 2 中,它返回一个列表。在 Python 2 中,使用 xrange 作为迭代器;在 Python 3 中,使用 list(range(...)) 作为列表。
  • 抱歉,我没有看到这个问题被标记为 Python-3。是的,@mlefavor 是正确的。
【解决方案4】:

这是一个迭代器。这里有大线索:它在 itertools 模块中。从您链接到的文档中:

itertools.repeat(object[, times]) 制作一个迭代器,一遍又一遍地返回对象。除非指定了 times 参数,否则无限期运行。

所以你永远不会在记忆中拥有所有这些东西。你想使用它的一个例子可能是

n = 25
t = 0
for x in itertools.repeat(4):
    if t > n:
        print t
    else:
        t += x

因为这将允许您任意数量的4s,或者任何您可能需要的无限列表。

【讨论】:

  • 您可以将第 3 行更改为 while True: 并将第 7 行的 x 更改为 4 ,它会做同样的事情,更具可读性,并且会稍微快一些。这就是为什么我想知道它是否有任何目的。
  • @TylerCrompton: 注意:有趣的是,在 Python 2 上,while True: 会比for x in itertools.repeat(4): 慢,因为当时True 不是关键字,所以while True: 实际加载了它并在每个循环上测试它的真实性,以确保没有人重新分配它(while 1: 是一个真正的无条件无限循环)。 repeat 将迭代器保留在堆栈中(在内置范围内没有查找)并保存了该工作。值得庆幸的是,在 Python 3 上,TrueFalse 是关键字,while True: 在字节码级别确实是一个无条件的无限循环。
【解决方案5】:

如前所述,它适用于zip

另一个例子:

from itertools import repeat

fruits = ['apples', 'oranges', 'bananas']

# Initialize inventory to zero for each fruit type.
inventory = dict( zip(fruits, repeat(0)) )

结果:

{'apples': 0, 'oranges': 0, 'bananas': 0}

要做到这一点而不重复,我必须让len(fruits)参与进来。

【讨论】:

  • inventory = {fruit: 0 for fruit in fruits} 更具可读性且速度稍快。
  • @TylerCrompton 确实如此。我不确定我之前是否使用过该语法来初始化字典。或者我只是使用了太多的 LINQ :-) 感谢您提供信息丰富的评论。
  • @TylerCrompton:如果我们追求速度,dict.fromkeys(fruits, 0) 是最快的(不是只有三个具有恒定值的项目,因为固定开销略高,而是随着项目的数量fruits 增加,dict.fromkeys 领先,从大约八项开始);在我的机器上渐近地运行,它的运行时间大约是 dict 理解大量输入的时间的 2/3。从 3.6 开始(保证为 dicts 排序),dict.fromkeys(x) 是一种在保持排序的同时唯一化输入的非常有效的方法(不像 set(x),它会丢失排序)。
【解决方案6】:

我通常将重复与链和循环结合使用。这是一个例子:

from itertools import chain,repeat,cycle

fruits = ['apples', 'oranges', 'bananas', 'pineapples','grapes',"berries"]

inventory = list(zip(fruits, chain(repeat(10,2),cycle(range(1,3)))))

print inventory

将前 2 个水果作为值 10,然后循环为剩余水果的值 1 和 2。

【讨论】:

    猜你喜欢
    • 2021-12-22
    • 2017-05-04
    • 2012-12-17
    • 2010-10-17
    • 2021-08-09
    • 2016-06-30
    • 1970-01-01
    • 2016-08-11
    相关资源
    最近更新 更多