Search code examples
pythonpython-3.xpython-itertools

What is the purpose of Python's itertools.repeat?


For every use I can think of for Python's itertools.repeat() class, I can think of another equally (possibly more) acceptable solution to achieve the same effect. For example:

>>> [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']

Is there any case in which itertools.repeat() would be the most appropriate solution? If so, under what circumstances?


Solution

  • The itertools.repeat function is lazy; it only uses the memory required for one item. On the other hand, the (a,) * n and [a] * n idioms create n copies of the object in memory. For five items, the multiplication idiom is probably better, but you might notice a resource problem if you had to repeat something, say, a million times.

    Still, it is hard to imagine many static uses for itertools.repeat. However, the fact that itertools.repeat is a function allows you to use it in many functional applications. For example, you might have some library function func which operates on an iterable of input. Sometimes, you might have pre-constructed lists of various items. Other times, you may just want to operate on a uniform list. If the list is big, itertools.repeat will save you memory.

    Finally, repeat makes possible the so-called "iterator algebra" described in the itertools documentation. Even the itertools module itself uses the repeat function. For example, the following code is given as an equivalent implementation of itertools.izip_longest (even though the real code is probably written in C). Note the use of repeat seven lines from the bottom:

    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