In the Effective Python book, the author recommends using assignment expressions to avoid redundancy in comprehensions, for example:
def fun(i):
return 2 * i
result = {x: y for x in [0, 1, 2, 3] if (y := fun(x)) > 3}
instead of
result = {x: fun(x) for x in [0, 1, 2, 3] if fun(x) > 3}
result
has the value {2: 4, 3: 6}
.
The author states that
If a comprehension uses the walrus operator in the value part of the comprehension and doesn’t have a condition, it’ll leak the loop variable into the containing scope. [...] It’s better not to leak loop variables, so I recommend using assignment expressions only in the condition part of a comprehension.
However, in the example above, y
is set to 6 at the end of the program. So, the variable in the assignment expression leaked, although it is defined in the condition.
The same thing happens for list comprehensions:
>>> _ = [(x, leak) for x in range(4) if (leak := 2 * x) > 3]
>>> leak
6
And even for generator expressions:
>>> it = ((x, leak) for x in range(4) if (leak := 2 * x) > 3)
>>> next(it)
(2, 4)
>>> leak
4
>>> next(it)
(3, 6)
>>> leak
6
What am I missing? Is there any way to avoid leaking in assignment expressions in comprehensions at all?
In Python, it's impossible not to leak loop variables
Unlike other languages such as C or Java, Python has no separate scope within
if
andfor
blocks. So when you use the:=
operator in anif
statement, afor
loop, or a list comprehension, the assigned variable will be in scope throughout the remainder of the function or class definition. This also means that after everyfor
loop, the loop variable will still be in scope and contain the value of the loop's last iteration.I disagree with the author of of Effective Python if he thinks that's a bad thing. "Leaking" loop variables can be very useful! Consider the following example:
However, there is one exception to this rule: implicit assignments made in list comprehensions have their own scope:
This exception is probably where your confusion stems from. It's a special case only, and not applicable when using the
:=
inside a list comprehension.