According to the list comprehension doc and this question: squares = [x**2 for x in range(10)]
is equivalent to squares = list(map(lambda x: x**2, range(10)))
But does Python actually implement list comprehension via map
and lambda
? Or is the squares = list(map(lambda x: x**2, range(10)))
in the document just an approximation instead of exactly equivalence?
If so, how to deal with iterating an enumerate
object? For example, squares = [(idx, x**2) for idx, x in enumerate(range(10))]
? I find it hard for me to imitate the previous example and re-write it using map
and lambda
.
If not(I tend to this option but not sure), is the list comprehension is implemented separately? Is there anything python sentence that is exactly equivalent to the list comprehension?
My research effort:
I tried squares = [print(locals()) for x in range(1)]
vs squares = list(map(lambda x: print(locals()), range(1)))
, inspried by this question eval fails in list comprehension. The first one will give {'.0': <range_iterator object at 0x000002AB976C4430>, 'x': 0}
while the latter gives {'x': 0}
. Perhaps this indicates that they are not exactly equivalence.
I also found these question:
They are using Python assembly to show the behavior of list comprehension. These answers perhaps indicate that the list comprehension is implemented separately.
So I tend to believe that the list comprehension is not implemented by map
and lambda
. However, I am not sure, and want to know "is there anything python sentence that is exactly equivalent to the list comprehension" as I mentioned above.
The origin of this question:
I am trying to answer a question. I want to explain strange behaviors related to the list comprehension without using python assembly. I used to think the list comprehension is implemented via lambda
in my previous answer. However, I am doubting about this now.
No, list comprehensions are not implemented by map and lambda under the hood, not in CPython and not in Pypy3 either.
CPython (3.9.13 here) compiles the list comprehension into a special code object that outputs a list and calls it as a function:
~ $ echo 'x = [a + 1 for a in [1, 2, 3, 4]]' | python3 -m dis
1 0 LOAD_CONST 0 (<code object <listcomp> at 0x107446f50, file "<stdin>", line 1>)
2 LOAD_CONST 1 ('<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_CONST 2 ((1, 2, 3, 4))
8 GET_ITER
10 CALL_FUNCTION 1
12 STORE_NAME 0 (x)
14 LOAD_CONST 3 (None)
16 RETURN_VALUE
Disassembly of <code object <listcomp> at 0x107446f50, file "<stdin>", line 1>:
1 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (a)
8 LOAD_FAST 1 (a)
10 LOAD_CONST 0 (1)
12 BINARY_ADD
14 LIST_APPEND 2
16 JUMP_ABSOLUTE 4
>> 18 RETURN_VALUE
Whereas the equivalent list(map(lambda: ...))
thing is just function calls:
~ $ echo 'x = list(map(lambda a: a + 1, [1, 2, 3, 4]))' | python3 -m dis
1 0 LOAD_NAME 0 (list)
2 LOAD_NAME 1 (map)
4 LOAD_CONST 0 (<code object <lambda> at 0x102701f50, file "<stdin>", line 1>)
6 LOAD_CONST 1 ('<lambda>')
8 MAKE_FUNCTION 0
10 BUILD_LIST 0
12 LOAD_CONST 2 ((1, 2, 3, 4))
14 LIST_EXTEND 1
16 CALL_FUNCTION 2
18 CALL_FUNCTION 1
20 STORE_NAME 2 (x)
22 LOAD_CONST 3 (None)
24 RETURN_VALUE
Disassembly of <code object <lambda> at 0x102701f50, file "<stdin>", line 1>:
1 0 LOAD_FAST 0 (a)
2 LOAD_CONST 1 (1)
4 BINARY_ADD
6 RETURN_VALUE