classification
Title: Nested set comprehensions in class scope fail
Type: behavior Stage: needs patch
Components: Interpreter Core Versions: Python 2.7
process
Status: closed Resolution: wont fix
Dependencies: Superseder: improper scope in list comprehension, when used in class declaration
View: 3692
Assigned To: Nosy List: Eric.Wieser, dmi.baranov, ezio.melotti, mark.dickinson
Priority: normal Keywords:

Created on 2013-05-31 22:22 by Eric.Wieser, last changed 2013-06-10 09:34 by Eric.Wieser. This issue is now closed.

Messages (5)
msg190420 - (view) Author: Eric Wieser (Eric.Wieser) * Date: 2013-05-31 22:22
This code:

    class Sudoku(dict):
        COLUMNS = [
            {(x, y) for y in xrange(9)} for x in xrange(9)
        ]

When run on Python 2.7.5, fails with this traceback:

    Traceback (most recent call last):
      File "<pyshell#3>", line 1, in <module>
        class Sudoku(object):
      File "<pyshell#3>", line 3, in Sudoku
        {(x, y) for y in xrange(9)} for x in xrange(9)
      File "<pyshell#3>", line 3, in <setcomp>
        {(x, y) for y in xrange(9)} for x in xrange(9)
    NameError: global name 'x' is not defined

Yet `x` is clearly not a global - it's defined in the comprehension.

Running the comprehension outside of the class gives no error:

    >>> [
        {(x, y) for y in xrange(9)} for x in xrange(9)
    ]
    [set([...]), ...]

Nor does using `set`:

    class Sudoku(dict):
        COLUMNS = [
            set((x, y) for y in xrange(9)) for x in xrange(9)
        ]
msg190444 - (view) Author: Eric Wieser (Eric.Wieser) * Date: 2013-06-01 08:54
This is not at first glance, a duplicate of 3692 - in that case, the list comprehension is referring to another class variable.

Most notably, that describes a behavioural change introduced by python 3 - in this case, python 3 handles it correctly - there's a bug in the python 2 implementation.
msg190890 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-06-10 07:44
While I agree that this isn't an exact duplicate of #3692, the underlying cause is the same, and Python is working as designed and documented in this case.

In Python 2, list comprehensions don't have their own scope, so the `x` in your initial example lives at class scope.  However, the set comprehension *does* have its own scope.  By design, a variable defined at class scope is not visible to inner scopes inside that class.

In Python 3, this works because the list comprehension has its own scope.

See the documentation here:

http://docs.python.org/2/reference/executionmodel.html#naming-and-binding

and the `class A` example in particular.

Reclosing: there's no way this behaviour going to change in Python 2, and this particular case is no longer an issue in Python 3.
msg190891 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-06-10 07:46
"won't fix" is probably a better resolution.  But in the absence of that apostrophe-rich option, I'll use "wont fix" instead.
msg190896 - (view) Author: Eric Wieser (Eric.Wieser) * Date: 2013-06-10 09:34
Thanks for the clarification - this behavior now makes perfect sense to me. As expected, swapping the list comprehension for a generator comprehension, or vice versa, prevents the error.
History
Date User Action Args
2013-06-10 09:34:25Eric.Wiesersetmessages: + msg190896
2013-06-10 07:46:19mark.dickinsonsetresolution: not a bug -> wont fix
messages: + msg190891
2013-06-10 07:44:56mark.dickinsonsetstatus: open -> closed

nosy: + mark.dickinson
messages: + msg190890

resolution: not a bug
2013-06-08 17:40:37ezio.melottisetnosy: + ezio.melotti

stage: needs patch
2013-06-01 08:54:43Eric.Wiesersetstatus: closed -> open
2013-06-01 08:54:23Eric.Wiesersetresolution: duplicate -> (no value)
messages: + msg190444
2013-06-01 03:50:15benjamin.petersonsetstatus: open -> closed
superseder: improper scope in list comprehension, when used in class declaration
resolution: duplicate
2013-05-31 23:30:49dmi.baranovsetnosy: + dmi.baranov
2013-05-31 22:22:08Eric.Wiesercreate