Holger Holger - 3 months ago 27
Python Question

Behavior of exec function in Python 2 and Python 3

Following code gives different output in

Python2
and in
Python3
:

from sys import version

print(version)

def execute(a, st):
b = 42
exec("b = {}\nprint('b:', b)".format(st))
print(b)
a = 1.
execute(a, "1.E6*a")


Python2
prints:

2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)]
('b:', 1000000.0)
1000000.0


Python3
prints:

3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42


Why does
Python2
bind the variable
b
inside the
execute
function to the values in the string of the
exec
function, while
Python3
doesn't do this? How can I achieve the behavior of
Python2
in
Python3
? I already tried to pass dictionaries for globals and locals to
exec
function in
Python3
, but nothing worked so far.

--- EDIT ---

After reading Martijns answer I further analyzed this with
Python3
. In following example I give the
locals()
dictionay as
d
to
exec
, but
d['b']
prints something else than just printing
b
.

from sys import version

print(version)

def execute(a, st):
b = 42
d = locals()
exec("b = {}\nprint('b:', b)".format(st), globals(), d)
print(b) # This prints 42
print(d['b']) # This prints 1000000.0
print(id(d) == id(locals())) # This prints True
a = 1.
execute(a, "1.E6*a")

3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42
1000000.0
True


The comparison of the ids of
d
and
locals()
shows that they are the same object. But under these conditions
b
should be the same as
d['b']
. What is wrong in my example?

Answer

There is a big difference between exec in Python 2 and exec() in Python 3. You are treating exec as a function, but it really is a statement in Python 2.

Because of this difference, you cannot change local variables in function scope in Python 3 using exec, even though it was possible in Python 2. Not even previously declared variables.

locals() only reflects local variables in one direction. The following never worked in either 2 or 3:

def foo():
    a = 'spam'
    locals()['a'] = 'ham'
    print(a)              # prints 'spam'

In Python 2, using the exec statement meant the compiler knew to switch off the local scope optimizations (switching from LOAD_FAST to LOAD_NAME for example, to look up variables in both the local and global scopes). With exec() being a function, that option is no longer available and function scopes are now always optimized.

Moreover, in Python 2, the exec statement explicitly copies all variables found in locals() back to the function locals using PyFrame_LocalsToFast, but only if no globals and locals parameters were supplied.

The proper work-around is to use a new namespace (a dictionary) for your exec() call:

def execute(a, st):
    namespace = {}
    exec("b = {}\nprint('b:', b)".format(st), namespace)
    print(namespace['b'])