-2

I'm playing around with dict comprehensions, and trying PEP572 (the := operator), in an example like this:

columns = {'idx', 'class_name'}
# populate them somehow
# ...
retval = {names[idx]:idx for idx in range(len(names)) if (names := list(columns))}

So basically, a dict that contains the item in the set and a value that iterates over it. However, names seems to be an UnboundLocalError.

The below is what I'm trying to do, capture an alias of an outside variable of the scope of the comprehension:

names = list(columns)
retval = {names[idx]:idx for idx in range(len(names))}

Why is that an error?

Edit

Trying this within the captured columns:

retval = {names[idx]:idx for idx in range(len(names:= list(columns)))}

produced a more meaningful IMHO error:

SyntaxError: assignment expression cannot be used in a comprehension iterable expression

So, as answered below, this isn't the way in which the walrus operator is meant to be used.

Ælex
  • 14,432
  • 20
  • 88
  • 129
  • 3
    ... obviously the `if` part is evaluated after the `for` part (otherwise what is there to filter on?), right? – user202729 Feb 04 '21 at 13:45
  • In your first code, you have never deifned a variable called names so the execution of range(len(names)) will fail as there is no vaiable called names – Chris Doyle Feb 04 '21 at 13:46
  • Can you please clarify what you think the first code is even supposed to do? – MisterMiyagi Feb 04 '21 at 13:49
  • You could produce the same result with a much simpler comprehension `retval1 = {val: index for index, val in enumerate(columns)}` – Chris Doyle Feb 04 '21 at 13:49
  • Just a side note, I think that if the `:=` is done in the first usage of the `name` (which is in the `range(len(names))` part) it would work. – user202729 Feb 04 '21 at 13:50

1 Answers1

3

You are misusing the condition. It is evaluated once per value retrieved from the iterator; it cannot be used to define the iterator. names isn't yet defined when you try to evaluate len(names).

Using range when you could use enumerate is a bit of anti-pattern anyway; try

retval = {name: idx for idx, name in enumerate(columns)}

Note that columns doesn't have a reliable ordering anyway; sets do not preserve the order in which items are added like dicts do.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • so the operator `:=` doesn't define new local variables within the scope of the comprehension, it's more of an alias? – Ælex Feb 04 '21 at 14:39
  • 1
    It does, but the semantics are different than for a assignment statement, because it's an ordinary expression. – chepner Feb 04 '21 at 14:41
  • (Even if it were like an assignment statement, it would still be undefined, even if it were marked as *going* to be local.) – chepner Feb 04 '21 at 14:42
  • AFAIK the iterated variable within a comprehension is the only visible variable outside the scope of the comprehension, thus I understood the assignment as possible because of that. I guess I'm wrong, the assignment is only accepted within the comprehension's scope. – Ælex Feb 04 '21 at 14:44
  • 1
    The iterated variable *might* be visible, but that's just a matter of whether it is a free variable or not. E.g., `[z for y in x for z in y]`: `x` is a free variable, so either has a value outside the comprehension or a `NameError` is raised. `y` is iterated over, but is local to the list comprehension. `:=` simply creates a new name in the scope, when and if the expression is evaluated. (The same for `=`, really, except the mere presence of an `=` is enough to shadow a global variable, whether or not the local name is ever defined.) – chepner Feb 04 '21 at 14:47