4

The below code snippet is getting different outputs from Python 2.7 and 3.3.

data = {'_out':[1,2,3,3,4]}
codes = ['_tmp=[]',
         '[_tmp.append(x) for x in _out if x not in _tmp]',
         'print(_tmp)']
for c in codes:
    exec(c,{},data)

Output from Python 2.7:

[1,2,3,4]

Output from Python 3.3:

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    exec(c,{},data)
  File "<string>", line 1, in <module>
  File "<string>", line 1, in <listcomp>
NameError: global name '_tmp' is not defined

To fix the error in Python 3.3, I simply set the globals to be as the same as locals, that is exec(c,data,data). Any idea why Python 3.3 is not behaving as that in 2.7?

pangyuteng
  • 1,749
  • 14
  • 29
  • 1
    even more interesting - it works just fine when using simple `for` loop instead of comprehension (which is by the way - incorrect way to operate with your list) – lejlot Jan 05 '16 at 23:29
  • Yup, for loop works, that's why I thought it was a bug. Thanks for the comprehensive answer. Issue 13557 is a great find! – pangyuteng Jan 06 '16 at 00:25
  • 1
    Two things or relevance here changed in 3.0. `Exec` is now a function instead of a statement. The body of comprehensions is now executed in a separate namespace instead of the namespace that contains the comprehension. – Terry Jan Reedy Jan 06 '16 at 02:53
  • 1
    Also see [What's the difference between eval, exec, and compile in Python?](http://stackoverflow.com/q/2220699/4014959), especially the excellent [answer](http://stackoverflow.com/a/29456463/4014959) by Antti Haapala. – PM 2Ring Jan 06 '16 at 06:45

1 Answers1

2

It appears to be known and desired behaviour, see issue 13557 https://bugs.python.org/issue13557

and further in

https://docs.python.org/3/reference/executionmodel.html#interaction-with-dynamic-features

The eval() and exec() functions do not have access to the full environment for resolving names. Names may be resolved in the local and global namespaces of the caller. Free variables are not resolved in the nearest enclosing namespace, but in the global namespace.

You can solve the above problem by not using list comprehension with local variables method calls, or by providing variables through global scope

Loops instead

data = {'_out':[1,2,3,3,4]}
codes = ['_tmp=[]', """
for x in _out: 
  if x not in _tmp: 
    _tmp.append(x)
""",
         'print(_tmp)']

for c in codes:
    exec(c, {}, data)

Global environment

data = {'_out':[1,2,3,3,4]}
codes = ['_tmp=[]',
         '[_tmp.append(x) for x in _out if x not in _tmp]',
         'print(_tmp)']
for c in codes:
    exec(c, data)
lejlot
  • 64,777
  • 8
  • 131
  • 164