16
votes

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

5
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
@MichałGórny Any non-error output would be good here... - Eric
It works fine on Python 3.2 (replacing xrange with range). It looks like a 2.7 bug (in set comprehensions backport). - Paolo Moretti
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. - user707650
This was a bug now resolved, update your python version. Issue 11796. - mg.

5 Answers

8
votes

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.

3
votes

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.
2
votes

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.

1
votes

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
1
votes

Maybe this it what you really want:

[[{x:y} for x in xrange(9)] for y in xrange(9)]