2

i was playing around (in relation with this question) with the walrus operator := which will be available in python 3.8.

with

def f(x):
    return x**3

old_list =  = list(range(9))

this works as expected (never mind that the example is pointless...):

new_list = [fx for x in old_list if (fx := f(x))  in {1, 8, 343}]
# [1, 8, 343]

this is valid and runs, but returns something unexpected:

new_list = [fx := f(x) for x in old_list if fx in {1, 8, 343}]
# []

what is happening here?

hiro protagonist
  • 44,693
  • 14
  • 86
  • 111

1 Answers1

4

TL;DR your 2nd one is not "that" valid:

>>> old_list = list(range(9))
>>> f = lambda x: x ** 3
>>> [fx := f(x) for x in old_list if fx in {1, 8, 343}]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
NameError: name 'fx' is not defined
>>> 

Explanation

In your first list comprehension, [fx for x in old_list if (fx := f(x)) in {1, 8, 343}], the fx := creates a variable outside of it:

>>> old_list = list(range(9))
>>> f = lambda x: x ** 3
>>> [fx for x in old_list if (fx := f(x))  in {1, 8, 343}]
[1, 8, 343]
>>> fx
512

If you run [fx := f(x) for x in old_list if fx in {1, 8, 343}] afterwards, it will go like this:

  • for x in old_list binds x to 0
  • if fx in {1, 8, 343}, fx being the old 512 which is false
  • as the if is false, the "select" (left) part is not executed, no call to f, no rebinding of fx, fx is still 512.
  • assign x as the 2nd element of old_list, being 1
  • ...

We can double check this interpretation like this:

>>> [print("no no no") for x in old_list if fx in {1, 8, 343}]
[]

no no no is never printed, so this side is never executed (as fx is 512, not in the set).

Julien Palard
  • 8,736
  • 2
  • 37
  • 44