Why doesn't iterating with `for` work while removing items from a list?

The list.pop() method will raise an exception if list length is zero.

if len(lst) > 0

The use of a while loop inside a for loop can be a tricky thing to manage, but first we should determine if that much logic is even needed. What is the most we need to iterate the list one time?

Answer: A single loop.

As for the textual return, that would never be advisable if the return is expected to be a list. It would be feeding garbage back to the caller and raise an exception as soon as a list method was called on that object. It would certainly be messing up the data, nonetheless.

>>> a = "string"
>>> b = a.copy()

Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    b = a.copy()
AttributeError: 'str' object has no attribute 'copy'
>>> b = list(a).copy()
>>> b
['s', 't', 'r', 'i', 'n', 'g']
>>> 

Here is a list of list methods and built in functions that work on lists, and perhaps other object types, as well.

Method Description
Python List append() Add Single Element to The List
Python List extend() Add Elements of a List to Another List
Python List insert() Inserts Element to The List
Python List remove() Removes Element from the List
Python List index() returns smallest index of element in list
Python List count() returns occurrences of element in a list
Python List pop() Removes Element at Given Index
Python List reverse() Reverses a List
Python List sort() sorts elements of a list
Python List copy() Returns Shallow Copy of a List
Python List clear() Removes all Items from the List
Python any() Checks if any Element of an Iterable is True
Python all() returns true when all elements in iterable is true
Python ascii() Returns String Containing Printable Representation
Python bool() Converts a Value to Boolean
Python enumerate() Returns an Enumerate Object
Python filter() constructs iterator from elements which are true
Python iter() returns iterator for an object
Python list() Function creates list in Python
Python len() Returns Length of an Object
Python max() returns largest element
Python min() returns smallest element
Python map() Applies Function and Returns a List
Python reversed() returns reversed iterator of a sequence
Python slice() creates a slice object specified by range()
Python sorted() returns sorted list from a given iterable
Python sum() Add items of an Iterable
Python zip() Returns an Iterator of Tuples

https://www.programiz.com/python-programming/methods/list

Why this does not work??

def delete_starting_evens(lst):
    for i in lst:
        if i % 2 > 0:
            break
        else:
            lst.remove(i)
    return lst

Blockquote

1 Like

i think one of the function calls provided by the exercise can help:

print(delete_starting_evens([4, 8, 10, 11, 12, 15]))

8 isn’t remove, this problem occurs when you have two even numbers after each other in the list and you remove from the same list as you are looping over

1 Like

#Write your function here
def delete_starting_evens(lst):
while (len(lst) > 0 and lst[0] % 2 == 0):
lst.pop(0)
return lst

def dse(lst):
for i in lst:
if len(lst) > 0 and lst[0] % 2 == 0:
lst.pop(0)
return lst

#Uncomment the lines below when your function is done
print(delete_starting_evens([4, 8, 10, 11, 12, 15]))
print(delete_starting_evens([4, 8, 10]))
print(dse([4, 8, 10]))

I tried it with the for loop in function defined as dse() and it does not pop out the 10 of the list, could anyone explain why is that? thank you

1 Like

When i is a positional element and we pop that element, all the elements to the right of that get shifted. The next element to be iterated in the list will be from one more to the right that it was expected to be, hence an element is skipped.

4, 8, 10    # i is 4
pop(0)
8, 10       # i is 10
pop(0)
10          # i is None

The return value will be [10].

2 Likes

The first solution that I have put #'s before works.
Why does the second solution leave [10] from a list of [4, 8, 10]? Why doesn’t it remove the 10 as well and give me ?

bank = [4]
bank.pop(0)
print(bank)
#the above gives me , so why does .pop(0) in a loop leave one number in the list?

2 Likes

looping over the same list as you are removing from is problematic.

lists can’t have empty spaces/slots, so the empty slot which was caused by the removal of an element is filled, while the loop continues to the next index, causing a value to be skipped

1 Like

Is there an order to which the values are skipped when this happens?

1 Like

The iterator moves forwards, same as ever.
You’re moving the values.

Also, you probably don’t want to pop at the front of the list. Do it at the back instead so that you don’t need to move all the other values every time.
(similarly you’re not going to want to copy all-but-the-first every iteration, better to find out where to slice, and then do it once)

…Except that there’s no need to modify the original, the simpler approach is to create a new list with the values to keep.

1 Like

Hello @stetim94,

I guess a lot of people have issues with the ways to solve this exercise. I have used all methods to test my knowledge and all worked: lst[1:], del, lst.remove(), but can not get my peace with .pop() method in for loop as the previous user had issue (it worked in while loop!).

It works in the following code:

def delete_starting_evens(lst):
  for number in lst:
    if number % 2 == 0:
      lst.pop(0)
    else:
      break
  return lst
print(delete_starting_evens([4, 8, 10, 11, 12, 15]))
# prints: [11, 12, 15]

but here doesn’t return an empty list as it supposed to:

print(delete_starting_evens([4, 8, 10]))
# prints: [10]

Why it can not .pop()out that last element?

Thank you for your “Why”!

1 Like

A for loop advances to the next element in the list, but if we remove (pop) the current element, the next one takes its place and the loop advances still to the next element.

Try using while.

while lst[0] % 2 == 0:
    lst.pop(0)
return lst
2 Likes

Thank you @mtf for your input - I have used while loop earlier and it worked for me. I was just puzzled why .pop() works in one instance and does not work in another (I would be OK to know if it would not work at all, but it does in the first lst example). When you are a beginner, such things just fry your brain :wink:

1 Like

The key is to keep note of the pointer of a for loop. Think in terms of next being loaded with the index following when reaching the current element, and it does not change when the list is shifted to the left.

1 Like

Thank you @mtf for your time! So according to this logic, what .pop() is trying to do in this instance of for loop:

print(delete_starting_evens([4, 8, 10]))

when loop tries to advance to the next item after 10, it sees that there’s no further item in the lst, so it can not actually .pop() out 10 because 10 has not been assigned 0 index?

Thank you!

1 Like

because it is at the next index on the right of 10, with 10 still being in list.

v
4  8  10

Once 4 is in play, the pointer advances to be ready for next.

   v
4  8  10

now we pop 4

    v
 8  10

Now 8 is in play and the pointer advances

        v
 8  10

The value in the iterator is 10, which is even, but 8 is the one that gets popped. There is no next so the loop terminates, leaving the 10 still in the list.

5 Likes

Thank you @mtf - visual now helped to clear the tangled-up logic in my mind, thank you for your time and patience!

2 Likes

I have thought about this and now think I understand it.
Writing the following helped me to understand it.
Is the following a correct illustration of what happens?

lst = [4, 5, 6, 7, 8, 9]
for number in lst:
INDENTlst.pop(0)
print(lst)
#the order of loops for this is:
#loop 1. -> 4 is popped
#loop 2. -> 5 moves to the slot vacated by the 4 (as lst[0] is currently empty)
#loop 3. -> 5 is popped
#loop 4. -> 6 moves to the slot vacated by the 5
#loop 5. -> 6 is popped
#loop 6. -> 7 moves to the slot vacated by the 6
#you’re out of loops
#lst is now [7, 8, 9]

the loop doesn’t make 6 iterations:

lst = [4, 5, 6, 7, 8, 9]
for number in lst:
    print "in loop:", lst
    lst.pop(0)
print(lst)

it only make threes, the occupation of an empty slot happens before going to next iteration of the loop

But there are 6 items in the list. Does that not mean 6 loops in a for loop for that 6-itemed list?

no, there are 6 items in your list before you start the loop

you mean 6 iterations of the loop? The answer: no.

because you remove items from the list, the loop won’t have to make 6 iterations to reach the end of the list