1

For loop with enumerate doesn't throw index out of range error while a an element is deleted inside loop?

L = [1, 4, 8, 5]
try:
  for i,item in enumerate(L): 
    print("Value of {} is {}".format(i, item))
    del L[i]
except IndexError as e:
    print("Index error: {err}.".format(err=e))

Output:

Value of 0 is 1
Value of 1 is 8

While this code causes the error

L = [1, 4, 8, 5]
try:
    for i in range(len(L)):
        print("Item:", L[i])
        del(L[i])
except IndexError as e:
    print("Error:", e)

Output:

Item: 1 
Item: 8
Error: list index out of range
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
Darshan
  • 94
  • 1
  • 8
  • I'm sorry, this isn't a duplicate (of that one at least). OP knows that there's a problem. OP just wants to know the difference of behaviour between the two. – Jean-François Fabre Mar 30 '18 at 14:01
  • 2
    I'd be surprised if we didn't have a proper duplicate somewhere, though -- variants on the question get asked a lot. – Charles Duffy Mar 30 '18 at 14:02
  • @Jean-FrançoisFabre Reopened, though I'm pretty sure the question I linked would help the OP, and virtually everyone else who gets here in the future. – Sven Marnach Mar 30 '18 at 14:04
  • @SvenMarnach if you check my answer, you'll see that I linked it as well :) – Jean-François Fabre Mar 30 '18 at 14:41
  • @CharlesDuffy there are questions asking what happens when removing while iterating. This one is (just slightly) different because it compares the behaviour of a dummy `for` loop using `range` with a `for` loop using an iterator. I found that different enough to answer it. – Jean-François Fabre Mar 30 '18 at 14:49

2 Answers2

1

Both codes are wrong (see How to remove items from a list while iterating?), but the symptoms are different in both cases.

In the first case, you're iterating on L. Shortening L makes iteration end faster (notice that some items are skipped, because shortening the list confuses the internal iterator, which raises a StopIteration, which stops the for loop)

You could create some equivalent code of the first case using a while loop:

L = [1, 4, 8, 5]
try:
    i = 0
    while i < len(L):
        print("Item:", L[i])
        del(L[i])
        i += 1
except IndexError as e:
    print("Error:", e)

or emulating the fact that for loop catches the StopIteration exception raised by next on the enumerate iterator:

e = enumerate(L)
while True:
try:
   i,item = next(e)
   print("Value of {} is {}".format(i, item))
   del L[i]
except StopIteration:
    break

you get the same result, because len(L) is evaluated at each iteration, so the index cannot get out of bounds (but the result isn't very useful either)

In the second case, you've computed range(len(L)) with the initial list size. So after a while, the index is out of range for the shortened list.

The first case doesn't crash, but the result is hard to understand, implementation defined, cannot be relied on. Don't do this.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • Thanks Jean. I knew the first case is bad code. Just learning to understand it better. What does enumerate do so skipping items on delete end up in index error. – Darshan Mar 30 '18 at 14:36
  • the `while` code I have added gives a good idea of what enumerate does. Well, it's not exactly like that (`enumerate` can work on a non-list iterable) but it looks very much like that. – Jean-François Fabre Mar 30 '18 at 14:43
  • Sorry to drag this further. I am just going mad :). From your statement : "Shortening L makes iteration end faster (notice that some items are skipped, because shortening the list confuses the internal iterator, which raises a StopIteration, which stops the for loop)". My question is simple, "Why exception StopIteration is not getting caught when this was iterated using for loop" – Darshan Mar 30 '18 at 17:17
  • @Darshan For loops consume the `StopIteration` exception. This exception is part of the iterator protocol and indicates the end of the iteration. A for loop creates an iterator for the iterable you pass in and calls `next()` on that iterator until `StopException` is raised, at which point the loop stops. – Sven Marnach Mar 30 '18 at 19:05
0

In the second code with range, the interpreter actually expands your range loop so it will try to print(L[2]) while your list has only two value.

runzhi xiao
  • 186
  • 6