Search code examples
pythonfor-loopenumerate

for loop using enumerate terminates unexpectedly


Here is a simple for loop through an enumerate object. This terminates due to (this line I have mentioned as a comment). Why is that?

enum_arr = enumerate(arr)
for ele in enum_arr:
    print(ele)
    print(list(enum_arr)[ele[0]:]) # terminates due to this line

Output:

(0, 0)
[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]

If I comment out the second print statement, then:

Output:

(0, 0)
(1, 1)
(2, 2)
(3, 3)
(4, 4)
(5, 5) 

As expected. Why is this happening?


Solution

  • enumerate() gives you an iterator object. Iterators are like a bookmark in a book that can only be moved forward; once you reach the end of the book you can't go back anymore, and have to make a new bookmark.

    You then use that iterator in two places; the for loop and list(). The list() function moved the bookmark all the way to the end, so the for loop can't move it any further.

    You'd have to create a new enumerate() object in the loop if you want to use a separate, independent iterator:

    enum_arr = enumerate(arr)
    for ele in enum_arr:
        print(ele)
        print(list(enumerate(arr[ele[0]:], ele[0])))
    

    This does require that arr is itself not an iterator, it has to be a sequence so you can index into it. I'm assuming here that you have a list, tuple, range or similar value.

    Note that I passed in ele[0] twice, the second argument to enumerate() lets you set the start value of the counter.

    It is easier to use a tuple assignment here to separate out the count and value:

    for count, value in enum_arr:
        print((count, value))
        print(list(enumerate(arr[count:], count)))
    

    Demo:

    >>> arr = range(6)
    >>> enum_arr = enumerate(arr)
    >>> for count, value in enum_arr:
    ...     print((count, value))
    ...     print(list(enumerate(arr[count:], count)))
    ...
    (0, 0)
    [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
    (1, 1)
    [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
    (2, 2)
    [(2, 2), (3, 3), (4, 4), (5, 5)]
    (3, 3)
    [(3, 3), (4, 4), (5, 5)]
    (4, 4)
    [(4, 4), (5, 5)]
    (5, 5)
    [(5, 5)]
    

    Coming back to the book analogy, and the requirement that arr is a sequence: as long as arr is a book with page numbers, you can add more bookmarks at any point. If it is some other iterable type, then you can't index into it and so would have to find some other means to 'skip ahead' and back again. Stretching the analogy further: say the book is being streamed to you, one page at a time, then you can't go back once you received all the pages. The solution coud be to create a local cache of pages first; if you can spare the memory that could be done with cached_copy = list(arr). Just take into account that you have to be sure that the book you are receiving is not so long as to require more space than you actually have. And some iterables are endless, so would require infinite memory!