17

I have this code in a file

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

When I run python broken.py, I get the traceback:

Traceback (most recent call last):
  File "U:\broken.py", line 1, in <module>
    class Sudoku(dict):
  File "U:\broken.py", line 3, in Sudoku
    {(x, y) for y in xrange(9)} for x in xrange(9)
  File "U:\broken.py", line 3, in <setcomp>
    {(x, y) for y in xrange(9)} for x in xrange(9)
NameError: global name 'x' is not defined
[Finished in 0.1s with exit code 1]

I don't really see the problem here. Isn't x defined in the comprehension?


What's stranger is how this seems to execute without an error when pasted directly into the python interpreter...


EDIT: This works if I use a list comprehension rather than a set comprehension

Eric
  • 95,302
  • 53
  • 242
  • 374
  • Just to make it clear: could you give the *exact* expected output? I'm just wondering if that's exactly what you want or if will you perform some more magic later. – Michał Górny Sep 04 '12 at 08:27
  • @MichałGórny Any non-error output would be good here... – Eric Sep 04 '12 at 08:28
  • Err, I'm just wondering if you exactly want sets-in-a-list. That seems a bit non-symmetrical :). – Michał Górny Sep 04 '12 at 08:31
  • This is very odd. It seems to be a very specific problem that occurs when using a nested set comprehension as a static member variable. All other combinations I have tried work, just not this particular combination. Dare I say a bug in Python? Gasp! – Lee Netherton Sep 04 '12 at 08:31
  • 1
    It works fine on Python 3.2 (replacing `xrange` with `range`). It looks like a 2.7 bug (in set comprehensions backport). – Paolo Moretti Sep 04 '12 at 08:41
  • @PaoloMoretti: yep, I'm on 2.7 here – Eric Sep 04 '12 at 08:44
  • I can reproduce this probelm with Python 2.7: It works in IPython and gives the exception when executed as a script. –  Sep 04 '12 at 08:45
  • Even worse - if there _is_ a global `x`, it will use that value instead of the one from the list comprehension. – John La Rooy Sep 04 '12 at 08:53
  • The problem is to do with the comprehension being inside the class definition. I suspect people seeing the code work in the interpreter are just trying the comprehension, not the class definition. – grifaton Sep 04 '12 at 09:10
  • 1
    Actually, using a 2.7 interpreter, it breaks as well. Make sure you include the full class definition, not just the line with the comprehension. The problem obviously is that the local variable `x` doesn't get transported into the short-hand set comprehension. I'd file it as a bug; see what happens. –  Sep 04 '12 at 09:13
  • 3
    This was a bug now resolved, update your python version. [Issue 11796](http://bugs.python.org/issue11796). – mg. Sep 04 '12 at 09:31
  • @mg: That bug seems to refer to something different - I'm not trying to refer to any class-level variable here. – Eric Sep 05 '12 at 07:59
  • @Eric: you are right, I was thinking that the outer list comprehension set the `x` variable in the class block. It is not, a list comprehension creates a new scope. – mg. Sep 06 '12 at 07:20

5 Answers5

8

I've filed a bug here. This is still broken by design in python 2.7.5.

From the bug report:

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.

Eric
  • 95,302
  • 53
  • 242
  • 374
3

Wild guess, but Python set comprehensions were introduced with Python 2.7 and 3.0: would you happen to use an older version to execute your script, and a recent one as your interpreter ?


After looking at this section of the Python documentation, I see no explanation for this behavior. Furthermore, the fact that it works with list comprehension clearly show that it is not a scope issue.

I see only two possible reasons:

  • A bug in the set comprehensions implementation
  • The use of an anterior Python version which does not support set comprehensions.
icecrime
  • 74,451
  • 13
  • 99
  • 111
2

I'm afraid I have no idea why your code is not working, however, the following works and gives you what you want:

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

Maybe some of the python gurus on this site can enlighten us as to why your code snippet fails.

Pascal Bugnion
  • 4,878
  • 1
  • 24
  • 29
1

I wish I could give a theoretical explanation, but this works:

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

if __name__ == "__main__":
    s = Sudoku()
    print s.COLUMNS
daedalus
  • 10,873
  • 5
  • 50
  • 71
  • 1
    i may be talking out of my a$$, but perhaps trying to define COLUMNS as a static member of the class makes the set comprehension loose scope and check for x in the global variables list (which does not make sense to me anyway you look at it). Perhaps report as a bug? – omu_negru Sep 04 '12 at 09:39
1

Maybe this it what you really want:

[[{x:y} for x in xrange(9)] for y in xrange(9)]
MaxPowers
  • 5,235
  • 2
  • 44
  • 69
  • 3
    There is nothing "apriori" wrong with the OP's set comprehension. If you cut it and paste it into an interpreter, it gives the expected answer. – Pascal Bugnion Sep 04 '12 at 08:29
  • 1
    My interpreter raised a SyntaxError, thats why I thought there is something else wrong. So I posted this answer, which works on my machine. Then I found out that it is just because I'm still using 2.6 :) – MaxPowers Sep 04 '12 at 08:51