st4rgut st4rgut - 2 months ago 8
Python Question

NameError when reordering for statements in a list comprehension

Hi please bear with me as I'm an amateur programmer. I'm learning list comprehensions and am getting 2 different results by switching variables though they look like they should work the same.

An array

a
equals
[[0, 0, 0, 0, 0], [1, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]


List Comprehension 1 Works:

[(i,j) for j in range(len(a[i])) for i in range(len(a))]


Returns:

[(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (0, 3), (1, 3), (2, 3), (3, 3), (4, 3), (0, 4), (1, 4), (2, 4), (3, 4), (4, 4)]


As expected.

But flipping the variables...

[(j,i) for i in range(len(a[j])) for j in range(len(a))]


Results in a NameError:name 'j' is not defined

Can someone please explain to me why it matters whether i or j comes first?

Answer

It's not the order of the variables that matters here. In fact, running List Comprehension 1 does not work either, for the same reason as List Comprehension 2. I'm guessing you had defined i earlier in the program, which is why List Comprehension 1 worked for you. The problem is the order of the for loops.

I'll try to explain by example. If you were to write it like this, it would run fine:

[ [(i,j) for j in range(len(a[i]))] for i in range(len(a))] 

Note the square brackets I added. In this case, the for loop with i happens first, and only then the for loop with j. (It should be noted, however, that this will return a list of lists of tuples.) Alternately, this would also run fine:

[(i,j) for i in range(len(a)) for j in range(len(a[i]))] 

When both for loops are written together that way (no extra brackets this time), they're read left-to-right.