As a by-product of wandering how list-comprehensions are actually implemented, I found out a good answer for your question.
In Python 2, take a look at the byte-code generated for a simple list comprehension:
>>> s = compile('[i for i in [1, 2, 3]]', '', 'exec')
>>> dis(s)
1 0 BUILD_LIST 0
3 LOAD_CONST 0 (1)
6 LOAD_CONST 1 (2)
9 LOAD_CONST 2 (3)
12 BUILD_LIST 3
15 GET_ITER
>> 16 FOR_ITER 12 (to 31)
19 STORE_NAME 0 (i)
22 LOAD_NAME 0 (i)
25 LIST_APPEND 2
28 JUMP_ABSOLUTE 16
>> 31 POP_TOP
32 LOAD_CONST 3 (None)
35 RETURN_VALUE
it essentially translates to a simple for-loop
, that's the syntactic sugar for it. As a result, the same semantics as for for-loops
apply:
a = []
for i in [1, 2, 3]
a.append(i)
print(i) # 3 leaky
In the list-comprehension case, (C)Python uses a "hidden list name" and a special instruction LIST_APPEND
to handle creation but really does nothing more than that.
So your question should generalize to why Python writes to the for loop variable in for-loop
s; that is nicely answered by a blog post from Eli Bendersky.
Python 3, as mentioned and by others, has changed the list-comprehension semantics to better match that of generators (by creating a separate code-object for the comprehension) and is essentially syntactic sugar for the following:
a = [i for i in [1, 2, 3]]
# equivalent to
def __f(it):
_ = []
for i in it
_.append(i)
return _
a = __f([1, 2, 3])
this won't leak because it doesn't run in the uppermost scope as the Python 2 equivalent does. The i
is leaked, only in __f
and then destroyed as a local variable to that function.
If you'd want, take a look at the byte-code generated for Python 3 by
running dis('a = [i for i in [1, 2, 3]]')
. You'll see how a "hidden" code-object is loaded and then a function call is made in the end.