0

Here is how the book tells you to write the code:

        for bullet in self.bullets.copy():
            if bullet.rect.bottom <= 0:
                self.bullets.remove(bullet)
        print(len(self.bullets))
        self._update_screen()

However doing this works just fine:

        for bullet in self.bullets.sprites():
            if bullet.rect.bottom <= 0:
                self.bullets.remove(bullet)
        print(len(self.bullets))
        self._update_screen()

So is there one that is internally better?

Cucho
  • 21
  • 3
  • Unless `sprites` is making an implicit copy, the book's way is better. You should not `remove` from an iterable while iterating over an iterator of it. It can cause elements to be skipped. They're making a copy likely to avoid that. If `sprites` is creating a shallow copy of `bullets`, then your way is fine as well. – Carcigenicate Jul 31 '21 at 16:00
  • @Carcigenicate This is a little trickier, though, as it's not immediately obvious that `sprites` is returning something that itself is iterating over `self.bullets`. `self.bullets` itself doesn't even have to be iterable for the code as shown to work. – chepner Jul 31 '21 at 16:06
  • @chepner Every definition of `sprites` I can find boils down to essentially `list(self.spritedict)` unless you know of an alternate definition. That means it should be safe to `remove`. This is pygame is it not? – Carcigenicate Jul 31 '21 at 16:12
  • 1
    I don't know any definition, because the question makes no reference to where anything is defined :) – chepner Jul 31 '21 at 16:12
  • Ya, that doesn't help. Assuming this is Pygame, `sprites` should be safe since it creates a shallow copy in every place I can find, unless an alternate definition of `sprites` is being provided in a subclass or something. – Carcigenicate Jul 31 '21 at 16:13

2 Answers2

1

Probably the main reason the tutorial tells you to make a shallow copy is because of the sometimes unexpected behavior of deleting an object in a for loop (for example you could get an out of bounds error depending on the exact implementation).

You should probably stick to the original version or look up other ways to remove elements from an array in a loop.

Example #1 - works

For example, this example removes all even numbers from a list:

arr = [i for i in range(10)]
print(arr) #prints: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

for x in arr:
    if x%2==0:
        arr.remove(x)
        
print(arr)#prints: [1, 3, 5, 7, 9]

Example #2 - IndexError

This attempts to do the same thing but throws an index error:

arr = [i for i in range(10)]
for x in range(len(arr)):
    if arr[x]%2==0:
        arr.remove(arr[x])
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-3-2b0b17a4b250> in <module>
     10 arr = [i for i in range(10)]
     11 for x in range(len(arr)):
---> 12     if arr[x]%2==0:
     13         arr.remove(arr[x])

IndexError: list index out of range

But you can get it to work by operating on a shallow copy:

import copy
arr = [i for i in range(10)]
c = copy.copy(arr) #create a shallow coppy
for x in range(len(arr)):
    if c[x]%2==0:# make accesses only to the copy
        arr.remove(c[x])#delete only by value (and not by index)
        
print(arr)#prints: [1, 3, 5, 7, 9]
Geo
  • 543
  • 5
  • 16
1

This is a bit of a stab in the dark, but I'll say, assuming:

  • This is pygame.
  • bullets isn't a custom subclass with a custom definition of sprites

Your way is fine (for now *).


They are likely using copy because you should not remove from a list (or any iterable) while iterating over an iterator of it. By making a copy, you're iterating over the copy while removing from the original, which is safe.

The question is then does sprites make a copy? I don't use pygame, but after searching through the source, it appears as though it does. Every definition I can find for it looks essentially like:

def sprites(self):
    return list(self.spritedict)

And list returns a shallow copy of the iterable given to it.


* That being said, this is also the docstring for sprites:

Returns an object that can be looped over with a 'for' loop. (For now, it is always a list, but this could change in a future version of pygame.)

Which suggests that they may change the implementation in the future. If they change the implementation to no longer produce a shallow copy, your code may break when you update the library later.

For the sake of being clear and preventing accidental breakage later, I'd go with the book's suggestion to use .copy.

Carcigenicate
  • 43,494
  • 9
  • 68
  • 117