4

I would expect the following loop to iterate six times, instead it iterates three with python3. I don't understand this behavior. I understand that the list changes as I delete elements but I don't get how that affects the for loop condition. Why is the loop iterating less than six times?

a = [1, 2, 3, 4, 5, 6]
for elem in a:
        del a[0]
        print(a)
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Zois Tasoulas
  • 1,242
  • 1
  • 11
  • 23

3 Answers3

4

You are deleting the first element in every iteration of the loop by del a[0], so the iterator is emptied in 3 steps, because it moves to the element after the one you removed on the next iteration. You can check the element the iterator is currently on, and the list status in the code below

a = [1, 2, 3, 4, 5, 6]
for elem in a:
    print(elem)
    del a[0]
    print(a)

The output is

1
[2, 3, 4, 5, 6]
3
[3, 4, 5, 6]
5
[4, 5, 6]

You can think of it as a pointer pointing to the first element of the list, that pointer jumps 2 steps when you delete the first element on each iteration, and it can jump only 3 times for 6 elements.

Generally it is a bad idea to modify the same list you are iterating on. But if you really want to, you can iterate over the copy of the list a[:] if you really want to delete items

a = [1, 2, 3, 4, 5, 6]
for elem in a[:]:
    del a[0]
    print(a)

The output is

[2, 3, 4, 5, 6]
[3, 4, 5, 6]
[4, 5, 6]
[5, 6]
[6]
[]
Devesh Kumar Singh
  • 20,259
  • 5
  • 21
  • 40
  • Hm, ok, I think I got it, thanks. So list a is checked before each iteration. After three iterations there will be only three elements left in the list so there is no fourth element to be fetched. I didn't get the expression "the iterator is emptied in 3 steps" at first but know I think I understand. Thanks. – Zois Tasoulas May 31 '19 at 15:04
  • think of it as a pointer pointing to the first element of the list, that pointer jumps 2 steps when you delete the first element on each iteration, and it can jump only 3 times for 6 elements @Zois – Devesh Kumar Singh May 31 '19 at 15:06
  • Thanks! Also, small typo on `del [0]`, `a` is missing. It won't allow me edit it. – Zois Tasoulas May 31 '19 at 15:08
2

The list iterator in CPython works by iterating over the positions of the list. You can think of it working like this:

def list_iter(items: list):
    index = 0
    while True:
        yield items[index]
        index += 1

In other words, iteration provides the item at 0, then 1, then 2, and so on. There is no pre-fetching of items - an item is looked up from the list when needed.

As you delete the first item on every step, the list is shortened by 1 on each step. Since you start with a list of 6 items, on the third iteration it is down to 3 items - meaning the fourth iteration fails to look up an item. Thus, your iteration finishes after three steps.

You can see this when printing also the current element in each loop. To visualise the effect, use enumerate to get the index of the iteration. Notice that it advances by one index, but the values are also shifted for a total offset of two:

>>> a = [1, 2, 3, 4, 5, 6]
... for idx, elem in enumerate(a):
...     print(elem, 'from', a)
...     print('      ', '   '*idx, '^')
...     del a[0]
...
1 from [1, 2, 3, 4, 5, 6]
        ^
3 from [2, 3, 4, 5, 6]
           ^
5 from [3, 4, 5, 6]
              ^

It is generally not well-defined to modify a container while iterating over it. You should iterate over a copy instead:

a = [1, 2, 3, 4, 5, 6]
for elem in a.copy():
    del a[0]
    print(a)
MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119
1

Iterating over and deleting elements from a list at the same time is tricky. One way to manage it is to traverse the list in reverse:

a = [1, 2, 3, 4, 5, 6]
for elem in reversed(a):
    print(a)
    del a[0]
print(a)

It'll print:

[1, 2, 3, 4, 5, 6]
[2, 3, 4, 5, 6]
[3, 4, 5, 6]
[4, 5, 6]
[5, 6]
[6]
[]
Óscar López
  • 232,561
  • 37
  • 312
  • 386