Python的itertools.repeat的目的是什么?

Tyl*_*ton 23 python python-itertools python-3.x

我可以想到的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']
Run Code Online (Sandbox Code Playgroud)

在任何情况下,它是最合适的解决方案吗?如果是这样,在什么情况下呢?

Ray*_*ger 24

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

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

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

for _ in itertools.repeat(None, 10000):
    do_something()
Run Code Online (Sandbox Code Playgroud)

这比以下更快:

for i in range(10000):
    do_something().
Run Code Online (Sandbox Code Playgroud)

前者获胜是因为它需要做的就是更新现有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)
Run Code Online (Sandbox Code Playgroud)

  • 这个答案和“重复”是很宝贵的。为什么将其隐藏在`itertools`中而不是内置的?对于range(x)中的_:do()来说,是一种常见的模式。 (2认同)
  • @Veky我的假设不是“Python被设计得很快”,也不必在任何意义上只是为了提供一个比“for _ in range(x): do()”更好的习惯用法,对于我不知道的情况不关心顺序,但是对于紧密循环有一些更快的东西会很好。我最初的评论是关于解释器和库,你的评论是关于语言设计,并且听起来好像使用除“range()”之外的任何东西都会以某种方式牺牲可读性以提高速度。Python 的可读性主要源于语法,而不是解释器附带的函数或它们的实现方式。 (2认同)

Har*_*wEm 20

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
Run Code Online (Sandbox Code Playgroud)

  • 轻微的狡辩:`[a]*n`不会在内存中创建n个副本.它创建了对a的单个副本的n个引用.在某些情况下,差异可能非常显着; 试试`a = [[]]*5; 一个[0] .append(1)`. (10认同)
  • 好点子.我一直忘记Python中几乎所有东西都是参考.我想这也有点减少了内存使用问题,但我猜想有一百万个引用仍然有一个非常重要的资源需求. (5认同)
  • 是的,它仍然需要分配一个n指针数组. (2认同)

Joh*_*lla 14

foo * 5看起来表面上看起来很像itertools.repeat(foo, 5),但它实际上是完全不同的.

如果你写作foo * 100000,翻译必须创建100,000份,foo然后才能给你答案.因此,这是一种非常昂贵且对记忆不友好的操作.

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

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