Search code examples
pythonpython-itertools

What is the point of itertools.count and itertools.islice?


I've looked at a online courses, and they have examples like the following:

from itertools import count
  
# creates a count iterator object
iterator =(count(start = 0, step = 2))
  
# prints an even list of integers
print("Even list:", 
      list(next(iterator) for _ in range(5)))

... which you could write using range or np.arange. Here's another example:

# list containing some strings
my_list =["x", "y", "z"]
  
# count spits out integers for 
# each value in my list
for i in zip(count(start = 1, step = 1), my_list):
    print(i)

... which is basically just enumerate. So my question is: can you give an example of itertools.count and itertools.islice that can't be done (or has to be done much more clunkily) using range?


Solution

  • Here's a situation where the count instance is used sporadically, not immediately in a single loop.

    class Foo:
        _x = count()  # Infinite supply of unique integer values
    
        def __init__(self):
            self._id = f'Foo #{next(self._x)}'
    

    Here's a case where islice is used to prevent O(n) memory usage:

    def is_sorted(some_list):
        return all(i <= j for i, j in zip(some_list, islice(some_list, 1, None)))
    

    If you had written that instead as

    def is_sorted(some_list):
        return all(i <= j for i, j in zip(some_list, some_list[1:]))
    

    you would have had to make nearly a full copy of some_list before even testing the first pair, which is a huge waste with a large list like [2, 1] + [3] * 10000.


    Neither one is necessary, in the sense that each is trivially definable:

    def count(start=0, step=1):
        while True:
            yield start
            start += step
    
    # A more accurate translation would be more complicated than necessary for our purposes here.
    # The real version would have to be able to handle stop=None
    # and choose 1 and -1 as default values for step, depending
    # on whether stop is less than or greater than start.
    def islice(itr, start, stop, step):
        for _ in range(start):
            next(itr)
    
        while start < stop:
            yield next(itr)
            start += step