Python for-in-loop stops iteration on list object created from for-in-loop

Viewed 1213

[First-ever question from first-ever Stack-o-flow Python beginner user]

I have been trying to create a function that appends numbers 1 to 10 and then erase from 10 to 1 in python list object. I succeeded however, I bumped into a weird behavior of for-in loops.

Below is what I did & succeeded:

def a():
    li = []

    for i in range(1,11):
        li.append(i)
        print(li)

    for n in range(1,11):
        li.remove(li[-1])
        print(li)

a()

would print:

[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5]
[1, 2, 3, 4]
[1, 2, 3]
[1, 2]
[1]
[]

However, if I change the second part of its for-loop range(1,11) to li, the iteration stops as shown below.

def a():
    li = []

    for i in range(1,11):
        li.append(i)
        print(li)

    for n in li:         <-------------- THIS PART
        li.remove(li[-1])
        print(li)

a()

Would print:

[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5]

So, I checked its length of the object li.

def a():
    li = []
    for i in range(1,11):
        li.append(i)
        print(li)

    print("Length before passed: ", len(li))

    for n in li: <------------ THIS
        print("Length BEFORE REMOVE: ", len(li))

        li.remove(li[-1])

        print("Length AFTER REMOVE: ", len(li))
        print(li)
a()

Would print:

[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Length before passed:  10
Length BEFORE REMOVE:  10
Length AFTER REMOVE:  9
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Length BEFORE REMOVE:  9
Length AFTER REMOVE:  8
[1, 2, 3, 4, 5, 6, 7, 8]
Length BEFORE REMOVE:  8
Length AFTER REMOVE:  7
[1, 2, 3, 4, 5, 6, 7]
Length BEFORE REMOVE:  7
Length AFTER REMOVE:  6
[1, 2, 3, 4, 5, 6]
Length BEFORE REMOVE:  6
Length AFTER REMOVE:  5
[1, 2, 3, 4, 5] <---- And it stops here.

What is going on here? Why would python for-loop stop before its full cycle?

Thank you very much.

4 Answers

In your case, the iteration stops because you're iterating from the start, but you're removing elements from the end at the same time:

  • The 1st iteration ends with 9 elements, so the loop proceeds to the 2nd element.
  • The 2nd iteration ends with 8 elements, so the loop proceeds to the 3rd element
  • ...
  • The 5th iteration ends with 5 elements, and there's no 6th element to proceed to and the loop ends.

This is why mutating a list while iterating it is generally inadvisable.

Try running this, you'll understand immediately

def a():
    li = []

    for i in range(1,11):
        li.append(i)
        print(li)

    for n in li:
        print(n)
        li.remove(li[-1])
        print(li)

a()

By removing elements this happens:

[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1
[1, 2, 3, 4, 5, 6, 7, 8, 9]
2
[1, 2, 3, 4, 5, 6, 7, 8]
3
[1, 2, 3, 4, 5, 6, 7]
4
[1, 2, 3, 4, 5, 6]
5
[1, 2, 3, 4, 5]

You add then print 10 times, you remove then print 10 times - simplified:

a = []
a.append(1)
print(a)         # [1]
a.pop()
print(a)         # []

You start with an empty list and print after adding - you stop with an empty list and print.

The problem with the second code part is: you are modifying an iterable while iterating - that is generally a bad idea.


If you change:

def a():
    li = []

    for i in range(1,11):
        li.append(i)
        print(li)

    while(li):
        li.pop()  # same but easier - removes last element without params
        if(li):   # test if non empty
            print(li)

or optimized:

    for _ in  range(len(li)-1): # one less so it stops bevore removing the last elem
        li.pop()
        print(li)

you get the desired output.

It's cause you are iterating the list from the start to the end and you are always removing the last element. As remove method alter the list and the for-in loop from python consider this alterations, you iterate over the five first itens and remove the five last ones.

My sugestion is do something like this:

def a():
  li = []

  for i in range(1,11):
    li.append(i)
    print(li)

  print("Length before passed: ", len(li))

  # Remove the last item while has items
  while len(li):
    li.pop()
Related