Python Forum
removing items from a list or group within a for loop.
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
removing items from a list or group within a for loop.
#1
Hello, hello.
I'm learning Python with a book(Python Crash Course), and at the part to code a game called 'Alien invasion'. To be more specific, currently I'm following to code bullets to be fired from a ship.

I have encountered this below.
# Get rid of bullets that have disappeared.
for bullet in self.bullets.copy():
    if bullet.rect.bottom <= 0:
        self.bullets.remove(bullet)
and, the author wrote,
"When you use a for loop with a list (or a group in Pygame), Python expects that the list will stay the same length as long as the loop is running. Because we can't remove items from a list or group within a for loop, we have to loop over a copy of the group. We use the copy() method to set up the for loop (line2), which enables us to modify bullets inside the loop."

I have 6 questions.

1 - why does Python like the length of a list to be the same within a for loop?
2 - is it common to keep the same length of list within for loop in programming languages?
3 - How does it possibly modify the original list by making a copy of it?????????!!!!!!!!!!!
4 - what about a while loop?
5 - what other solution is out there to modify a list within a for loop?
6 - is this sentence "it is not safe to modify the list during an iterative looping" the same as the sentence "Python expects that the list will stay the same length as long as the loop is running"?

Thank you in advance.
Reply
#2
1: Say you are looping through a list. You get to the second item in the list, and decide to remove it. When the loop iterates again, it goes to the third item in the list. But the third item is now what the fourth item was before you removed the second item, and you just skipped over what was the third item because it is now the second item. If you find this confusing, so does Python.

2. I don't know.

3. It loops over the copy of the list, but then when it does the removal, it does that to the original list.

4. You could do that, but I expect it would be more complicated than looping over a copy.

5. I'm not used to seeing people make copies to loop over. More often I see people building new lists:

new_list = []
for item in old_list:
    if is_valid(item):
        new_list.append(item)
old_list = new_list
6. I'm not sure what Python expects, but it not being safe to mess with what you are looping over is a great way to think about it.
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures
Reply
#3
(Nov-10-2019, 09:09 PM)ichabod801 Wrote: 1: If you find this confusing, so does Python.

3. It loops over the copy of the list, but then when it does the removal, it does that to the original list.

5. I'm not used to seeing people make copies to loop over. More often I see people building new lists:

new_list = []
for item in old_list:
    if is_valid(item):
        new_list.append(item)
old_list = new_list
6. I'm not sure what Python expects, but it not being safe to mess with what you are looping over is a great way to think about it.
1: Okay, I got it. Thank you.

3: The point of removing was not to slow down the program because the bullets will keep adding up. Isn't it counter-intuitive then to make a copy? because a copy of un-deleted bullets will still reside? What am I missing?

5. I need some time to understand your codes... keeping look at 'em 'til I digest but thank you.

6. Okay, got it.
Reply
#4
A while loop that starts at the END of the list and works backward will avoid the skipping problem and is considered "safer", kind of like bungie jumping is safer than cliff diving. Also, you can just remove items using that remove function but I think that would miss the point the instructor is trying to make.
lst = [1,2,3,4,5,6,7]
start = len(lst)-1
while start > -1 :
    if lst[start] == 4 :
        lst.remove(lst[start])
    start -= 1
print (lst)
lst = [1,2,3,4,5,6,7]
lst.remove(3)
print(lst)
Output:
[1, 2, 3, 5, 6, 7] [1, 2, 4, 5, 6, 7]
Reply
#5
(Nov-11-2019, 12:37 AM)allusernametaken Wrote: 3: The point of removing was not to slow down the program because the bullets will keep adding up. Isn't it counter-intuitive then to make a copy? because a copy of un-deleted bullets will still reside? What am I missing?

The copy will reside for a while. But as soon as the loop is done, all the references to the copy will be gone. As soon as garbage collection happens, that memory will be freed up. Also, the copy is going to be a copy of references to the objects, not full copies of all the objects in the list. So it takes up a lot less space than keeping those objects around would. It also avoids any processing time associated with those objects, such as checking them for collision events.
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures
Reply
#6
This is the most pythonic way to do such a task.
# Get rid of bullets that have disappeared.
self.bullets = [bullet for bullet in self.bullets if bullet.rect.bottom > 0]
Reply
#7
Typing >>> help('for') into interactive interpreter will give pretty comprehensive description about for-loop behaviour. For example:

Quote:There is a subtlety when the sequence is being modified by the
loop (this can only occur for mutable sequences, e.g. lists). An
internal counter is used to keep track of which item is used next,
and this is incremented on each iteration. When this counter has
reached the length of the sequence the loop terminates. This means
that if the suite deletes the current (or a previous) item from the
sequence, the next item will be skipped (since it gets the index of
the current item which has already been treated). Likewise, if the
suite inserts an item in the sequence before the current item, the
current item will be treated again the next time through the loop.
This can lead to nasty bugs that can be avoided by making a
temporary copy using a slice of the whole sequence, e.g.,

     for x in a[:]:
         if x < 0: a.remove(x)
I'm not 'in'-sane. Indeed, I am so far 'out' of sane that you appear a tiny blip on the distant coast of sanity. Bucky Katt, Get Fuzzy

Da Bishop: There's a dead bishop on the landing. I don't know who keeps bringing them in here. ....but society is to blame.
Reply
#8
Hi everyone, I'm the author of PCC. This was an interesting issue to work through when I was first writing the book. I spent a fair bit of time thinking about how much to explain this bit of code.

One thing to realize is that bullets is an instance of pygame.sprite.Group; it is not a list. So you can't just make a new group with a comprehension. If you wanted to make a new group I believe you'd need to write something like this:

# Keep only the bullets that are still live.
remaining_bullets = Group()
for bullet in self.bullets:
    if bullet.rect.bottom >= 0:
        self.remaining_bullets.add(bullet)
self.bullets = remaining_bullets
That's more verbose, and less clear than the original code. You can use a comprehension to make a list of dead bullets, then loop through that list and remove those bullets from the group bullets:

dead_bullets = [bullet for bullet in self.bullets if bullet.rect.bottom <= 0]
for bullet in dead_bullets:
    self.bullets.remove(bullet)
I didn't find this approach particularly appealing when I first wrote the book, and I'm still not convinced this is better than what's in the text.

What do people think?
Reply
#9
I don't see that verbose is necessarily worse, especially if you are looking for clarity. I don't see that it's less clear, either. If I am reading the Pygame docs correctly, you could do a list comprehension:

self.bullets = Group(*[bullet for bullet in self.bullets if bullet.rect.bottom <= 0])
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures
Reply
#10
I have read all the replies multiples times since yesterday, although I still understand only 50%. It's very cool to see the different approaches depends on different understanding. Most important lesson I got from this discussion is to read the documentation. Thank you all for the inspiration.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  How to parse and group hierarchical list items from an unindented string in Python? ann23fr 0 70 Yesterday, 01:16 PM
Last Post: ann23fr
  Why do I have to repeat items in list slices in order to make this work? Pythonica 7 1,256 May-22-2023, 10:39 PM
Last Post: ICanIBB
  Finding combinations of list of items (30 or so) LynnS 1 836 Jan-25-2023, 02:57 PM
Last Post: deanhystad
  For Word, Count in List (Counts.Items()) new_coder_231013 6 2,497 Jul-21-2022, 02:51 PM
Last Post: new_coder_231013
  How to get list of exactly 10 items? Mark17 1 2,400 May-26-2022, 01:37 PM
Last Post: Mark17
  how to assign items from a list to a dictionary CompleteNewb 3 1,531 Mar-19-2022, 01:25 AM
Last Post: deanhystad
  Reading list items without brackets and quotes jesse68 6 4,518 Jan-14-2022, 07:07 PM
Last Post: jesse68
Question How to gather specific second-level items from a list chatguy 2 1,516 Dec-17-2021, 05:05 PM
Last Post: chatguy
  deleting select items from a list Skaperen 13 4,389 Oct-11-2021, 01:02 AM
Last Post: Skaperen
  Getting All Items From A List knight2000 4 2,351 Sep-25-2021, 12:56 AM
Last Post: knight2000

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020