[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
Re: [pygame] more list problems
Although I think Noah provided the "correct" solution, which is to
create a new list by filtering the input list, I figured you also
might want to know why your solution below doesn't work as you intend.
Iterating over changing mutable sequences is tricky because as the
list changes, the indexes of elements in the list can also change.
Imagine the following list of rects:
rect = [Rect(10,10,5,5), Rect(0,0,10,10), Rect(7,7,3,3)]
Now imagine a "click" at position (8,8)
range(0, len(rect)) returns [0, 1, 2] (Note you can omit the first
argument, but default range() starts at index 0, thus range(len
(rect)) is equivalent)
The first time though the loop, i=0. The mouse position is not inside
rect[0], so we move on.
Now i=1, and the mouse is inside rect[1], so we now execute 'del rect
[1]', thus changing the list to:
[Rect(10,10,5,5), Rect(7,7,3,3)]
The next time through the loop i=2, but now the rect list does not
have an index 2! So it blows up with an IndexError. Note this is not
the only possible outcome. Had my list been longer I would have wound
up skipping the original element at index 2 altogether, meaning it
would never be checked. Those are the kinds of bugs that can make you
tear your hair out 8^)
So assuming we needed to mutate the list in this way (and not filter
it into a new one as Noah suggested), what could we do?
Let me start by saying that anytime I find myself writing a for loop
over a contrived range(), I pause and think for a second, because in
Python that is almost never the best way to iterate something. It's a
bit of a red flag. That's not to say I never do it, it's just not
typical, even though it's often the only way to do loops in other
languages.
To fix the above, I would suggest iterating over a copy of the
original list instead of the range(). That way you can mutate the
original list without upsetting the loop. you can create a (shallow)
copy of a list by passing it to the built-in list():
for r in list(rect): # iterate over a copy of rect list
if r.collidepoint(mousexTEMP, mouseyTEMP):
rect.remove(r)
Note I omitted the numRect calculation. Can't you just use len(rect)
at the end of the loop?
Now having "solved" the problem of mutating the rect list in this
way, I have to say that Noahs solution is more elegant, thus superior
to the above. It doesn't mutate the list in place at all, thus
skirting the problem entirely, plus it's much shorter.
The problem you encountered is a very common python coding pitfall.
The best way to avoid it is to think twice before mutating a sequence
while iterating it.
hth,
-Casey
On Sep 20, 2007, at 5:18 PM, Eric Hunter wrote:
so I have been able to write pygame.Rect rectangles into rect-type
lists. but now I'm trying to delete unwanted rectangles that are
printed to the screen.
when I draw one at a time, I can only delete the last drawn
rectangle before deleting the previous ones. I want to be able to
delete any rectangle at any time without problems. I've come close
but have run into a wall.
below is the "deleting rect" section. if anyone needs more then
what's provided i can provide it upon request.
thanks in advance.
[code]
if event.button == 3:
mousexTEMP, mouseyTEMP =
pygame.mouse.get_pos()
for i in range(0, len(rect)):
if rect[i].collidepoint
(mousexTEMP, mouseyTEMP):
del rect[i]
numRect -= 1
[/code]