Search code examples
pythonpython-3.xloopsfor-loopinfinite-loop

Infinite for loop in Python - is the list updated or not?


First of all, I am aware that I should use while loop to create an infinite one. Nevertheless, while playing with making an infinite for loop in Python I run into something I do not fully understand.

My idea for an "infinite" for loop was following:

l = [1]
for el in l:
  print(len(l))
  l.append(1)

and this in fact created an infinite loop as 1 was constantly appended to the list l at each loop iteration. So I thought I do not want my list to become longer and longer so that at some point I have not enough memory. So I did this:

l = [1]
for el in l:
  print(len(l))
  l.pop(0)
  l.append(1)

and I got just one iteration. Can someone explain why? Is it because the l is still referenced to the same object and its length is always 1?


Solution

  • The for statement returns an "iterator" which is a container for a stream of data that returns one element at a time, so you are not iterating over the iterable (your list in this case) but instead over this container.

    You can return this iterator directly using iter() in order to better understand what is happening with your for loop.

    items = ['a']
    
    it = iter(items)
    print(it.__reduce__())
    # (<built-in function iter>, (['a'],), 0)
    

    The built in __reduce__() function returns state information about the iterator. Note that the state information returned immediately after the iterator is created includes the iterable, ['a'], and an index value of 0. The index value indicates the position of the next element that will be returned by the iterator.

    To simulate the first iteration of the loop, we can use next() which returns the next element from an iterator.

    next(it)
    print(it.__reduce__())
    # (<built-in function iter>, (['a'],), 1)
    

    Now we see that the index value has changed to 1 because the iterator already returned the first element in the list and on the next iteration it will attempt to return the second element in the list. If you attempt to remove the first element in the list and then append another element to the list, following is the resulting state of the iterator.

    items.pop(0)
    items.append('b')
    print(it.__reduce__())
    # (<built-in function iter>, (['b'],), 1)
    

    You can see that the first element was removed and the new element was appended (as expected). However, the iterator still retained an index value of 1 as the position of the next element to be returned from iteration. If we attempt another iteration, a StopIteration exception will be raised because there is no element at index 1 in the iterable being used by our iterator container.

    next(it)
    # Traceback (most recent call last):
    #   File "main.py", line 16, in <module>
    #     next(it)
    # StopIteration
    

    If you are really interested in creating an infinite for loop, using a generator would be a better way to deal with your memory concerns (although as you note in your question, there aren't too many good reasons not to use while for this sort of thing). See my answer to a related question for an example of an infinite for loop.