3

Why does reversing the order of loops in this comprehension yield a different result when used with a comprehension filter?

print len([cell for row in cells for cell in row])
print len([cell for cell in row for row in cells])
print len([cell for row in cells for cell in row if cell.rect.collidepoint(pos)])
print len([cell for cell in row for row in cells if cell.rect.collidepoint(pos)])

prints:

192
192
1
0
Miles
  • 1,357
  • 2
  • 12
  • 20
  • 2
    Loops are nested from left to right, so the first `for` is the outer loop. That means either `row` or `cells` needs to be defined before the loop runs for the comprehension to work. What are the contents of those variables? – Martijn Pieters Aug 11 '13 at 18:42
  • @MartijnPieters: The values leaked from the first comprehension, which does execute! – Eric Aug 11 '13 at 18:44
  • @Eric: Quite possibly, and I already voted for your answer. But without further context it is still just speculation. – Martijn Pieters Aug 11 '13 at 18:45

1 Answers1

4

Try running this code in a fresh interpreter:

>>> cells = [[1, 2, 3], [4, 5, 6]]
>>> [cell for cell in row for row in cells]
NameError: name 'row' is not defined

It works in your code because something's defined a row variable. But what?

in python 2.x, list comprehensions leak local variables (generator, set, and dictionary comprehensions, or 3's list comprehensions, do not)

In the following:

>>> [cell for row in cells for cell in row]
[1, 2, 3, 4, 5, 6]
>>> row
[4, 5, 6]
>>> cell
6

cell and row are added to the local scope. When you run the next test case, you're unintentionally using this old value of row:

>>> [cell for cell in row for row in cells ]
[4, 4, 5, 5, 6, 6]

This is only true of list comprehensions in python 2.x.

One way to avoid being caught out by this is to use list(... for x in ...) instead of [... for x in ...].

Eric
  • 95,302
  • 53
  • 242
  • 374
  • This can sometimes lead to some [very peculiar](http://stackoverflow.com/q/12259251/102441) problems – Eric Aug 11 '13 at 18:44