Check bytecode with dis.dis
:
>>> dis('''class T:
... l = 3
... t = [(i, j) for i in range(l) for j in range(l)]''')
For the list comprehension in the class, the generated bytecode is as follows:
Disassembly of <code object <listcomp> at 0x000002B84F04ED90, file "<dis>", line 3>:
3 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0) # load iterator of "range(l)" of the outer for loop
>> 4 FOR_ITER 13 (to 32)
6 STORE_FAST 1 (i)
8 LOAD_GLOBAL 0 (range)
10 LOAD_GLOBAL 1 (l) # load "l" of the inner for loop
12 CALL_FUNCTION 1
14 GET_ITER
>> 16 FOR_ITER 6 (to 30)
18 STORE_FAST 2 (j)
20 LOAD_FAST 1 (i)
22 LOAD_FAST 2 (j)
24 BUILD_TUPLE 2
26 LIST_APPEND 3
28 JUMP_ABSOLUTE 8 (to 16)
>> 30 JUMP_ABSOLUTE 2 (to 4)
>> 32 RETURN_VALUE
Notice the two comments I marked, which correspond to the bytecode of load the iterator of range(l)
in the outer for loop and the bytecode of load l
in the inner for loop respectively. The obvious difference is that the iterator of range(l)
in the outer for loop is passed into the list comprehension as a function parameter, while the inner range(l)
needs to be loaded dynamically. This attempts to find them from the global namespace, because l
is not in the global namespace but in the private namespace of the class T. Therefore, the list comprehension cannot find it, resulting in an error.
Some different from list comprehension in functions:
A well-known thing is that the same code will not make errors in the definition body of the function. This time, check the bytecode of the function with the same definition body:
>>> dis('''def foo():
... l = 3
... t = [(i, j) for i in range(l) for j in range(l)]''')
Disassembly of <code object <listcomp> at 0x000002B850EF5A50, file "<dis>", line 3>:
3 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0) # load iterator of "range(l)" of the outer for loop
>> 4 FOR_ITER 13 (to 32)
6 STORE_FAST 1 (i)
8 LOAD_GLOBAL 0 (range)
10 LOAD_DEREF 0 (l) # load "l" of the inner for loop
12 CALL_FUNCTION 1
14 GET_ITER
>> 16 FOR_ITER 6 (to 30)
18 STORE_FAST 2 (j)
20 LOAD_FAST 1 (i)
22 LOAD_FAST 2 (j)
24 BUILD_TUPLE 2
26 LIST_APPEND 3
28 JUMP_ABSOLUTE 8 (to 16)
>> 30 JUMP_ABSOLUTE 2 (to 4)
>> 32 RETURN_VALUE
Note that the loading method of l
in the inner loop is different from that in the class definition body. Python treats l
here as closure, the bytecode of the function is here:
Disassembly of <code object foo at 0x000002B850EF5FD0, file "<dis>", line 1>:
2 0 LOAD_CONST 1 (3)
2 STORE_DEREF 0 (l)
3 4 LOAD_CLOSURE 0 (l) # treats "l" as closure
6 BUILD_TUPLE 1
8 LOAD_CONST 2 (<code object <listcomp> at 0x000002B850EF5A50, file "<dis>", line 3>)
10 LOAD_CONST 3 ('foo.<locals>.<listcomp>')
12 MAKE_FUNCTION 8 (closure)
14 LOAD_GLOBAL 0 (range)
16 LOAD_DEREF 0 (l)
18 CALL_FUNCTION 1
20 GET_ITER
22 CALL_FUNCTION 1
24 STORE_FAST 0 (t)
26 LOAD_CONST 0 (None)
28 RETURN_VALUE