On 6 June 2018 at 15:31, Rob Cliffe via Python-Dev <python-dev@python.org> wrote:

Is this a bug or a feature?
Consider the following program:

# TestProgram.py
def Test():
  # global x
    x = 1
    exec(compile('print([x+1,x+2])', 'MyTest', 'exec'))
    exec(compile('print([x+i for i in range(1,3)])', 'MyTest', 'exec'))
Test()

In Python 2.7.15 the output is

[2, 3]
[2, 3]

In Python 3.6.5 the output is
[2, 3]
Traceback (most recent call last):
  File "TestProgram.py", line 7, in <module>
    Test()
  File "TestProgram.py", line 6, in Test
    exec(compile('print([x+i for i in range(1,3)])', 'MyTest', 'exec'))
  File "MyTest", line 1, in <module>
  File "MyTest", line 1, in <listcomp>
NameError: name 'x' is not defined

If the "global x" declaration is uncommented, this "fixes" the Python 3.6.5 behaviour,
i.e. no error occurs and the output is the same as for Python 2.7.15.

In other words, it looks as if in Python 3.6.5, the compiled list comprehension
can "see" a pre-existing global variable but not a local one.

Yes, this is expected behaviour - the two-namespace form of exec (which is what you get implicitly when you use it inside a function body) is similar to a class body, and hence nested functions (including the implicit ones created for comprehensions) can't see the top level local variables.

You can override that and force the use of the single-namespace form by passing the locals() namespace into exec() explicitly:

    def explicit_local_namespace():
        x = 1
        exec(compile('print([x+i for i in range(1,3)])', 'MyTest', 'exec'), locals())
    explicit_local_namespace()

(Note: you'll then need to use collections.ChainMap instead of separate locals and globals namespaces if you want the exec'ed code to still be able to see the module globals in addition to the function locals)

Cheers,
Nick.

--
Nick Coghlan   |   ncoghlan@gmail.com   |   Brisbane, Australia