classification
Title: dict comprehension shouldn't raise UnboundLocalError
Type: behavior Stage: resolved
Components: Interpreter Core Versions: Python 3.6
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: eryksun, ksqsf, r.david.murray
Priority: normal Keywords:

Created on 2017-08-06 13:17 by ksqsf, last changed 2017-08-06 23:07 by eryksun. This issue is now closed.

Messages (5)
msg299799 - (view) Author: ksqsf (ksqsf) Date: 2017-08-06 13:17
The code
    key = ["a", "b"]
    val = [1, 2]
    dic = {key:val for key in key for val in val}
will raise UnboundLocalError in Python 3.6.2 and 2.7.13.

Intuitively, the element 'key' and the list 'key' are not the same, so generally the expected result is {"a": 1, "b": 2}.

There are similar cases for listcomps, setcomps and genexprs:
    l = [1, 2, 3]
    {l for l in l}   # => {1, 2, 3}
    [l for l in l]   # => [1, 2, 3]
    for l in (l for l in l):
        print(l, end=' ')
                     # => 1 2 3 
All of them do as what is expected. 

For consistency and intuitiveness, the behavior of distcomps should be modified so that no UnboundLocalError is raised.
msg299800 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2017-08-06 14:07
The behavior is consistent:

>>> a = [1, 2]
>>> b = [3, 4]
>>> [(a, b) for a in a for b in b]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
UnboundLocalError: local variable 'b' referenced before assignment


I'm not sure why it is only the nested loop that raises the error (https://docs.python.org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries seems to imply it should raise for both)

By the way, the actual result of your comprehesion would be {"a": 2, "b" 2}.
msg299801 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2017-08-06 14:26
Comprehensions evaluate the iterator for the outermost loop in the surrounding scope. The iterators for all inner loops are evaluated in the local scope of the comprehension itself.
msg299807 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2017-08-06 18:05
I wonder if that explanation should be added to the doc section to which I pointed.  I thought I'd remembered something like that being in there, but it isn't.
msg299818 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2017-08-06 23:07
It's consistent with the behavior of generator expressions:

    Variables used in the generator expression are evaluated lazily
    when the __next__() method is called for the generator object
    (in the same fashion as normal generators). However, the
    leftmost for clause is immediately evaluated, so that an error
    produced by it can be seen before any other possible error in
    the code that handles the generator expression. Subsequent for
    clauses cannot be evaluated immediately since they may depend
    on the previous for loop. For example: (x*y for x in range(10)
    for y in bar(x)).
History
Date User Action Args
2017-08-06 23:07:21eryksunsetmessages: + msg299818
2017-08-06 18:05:21r.david.murraysetmessages: + msg299807
2017-08-06 14:26:54eryksunsetstatus: open -> closed

nosy: + eryksun
messages: + msg299801

resolution: not a bug
stage: resolved
2017-08-06 14:07:19r.david.murraysetnosy: + r.david.murray
messages: + msg299800
2017-08-06 13:17:16ksqsfcreate