Videos
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'])
The exec() documentation is very explicit about this limitation:
Note: The default locals act as described for function
locals()below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after functionexec()returns.
I'd say it's a bug of python3.
def u():
exec("a=2")
print(locals()['a'])
u()
prints "2".
def u():
exec("a=2")
a=2
print(a)
u()
prints "2".
But
def u():
exec("a=2")
print(locals()['a'])
a=2
u()
fails with
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in u
KeyError: 'a'
--- EDIT --- Another interesting behaviour:
def u():
a=1
l=locals()
exec("a=2")
print(l)
u()
def u():
a=1
l=locals()
exec("a=2")
locals()
print(l)
u()
outputs
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 1}
And also
def u():
l=locals()
exec("a=2")
print(l)
print(locals())
u()
def u():
l=locals()
exec("a=2")
print(l)
print(locals())
a=1
u()
outputs
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}}
Apparently, the action of exec on locals is the following:
- If a variable is set within
execand this variable was a local variable, thenexecmodifies the internal dictionary (the one returned bylocals()) and does not return it to its original state. A call tolocals()updates the dictionary (as documented in section 2 of python documentation), and the value set withinexecis forgotten. The need of callinglocals()to update the dictionary is not a bug of python3, because it is documented, but it is not intuitive. Moreover, the fact that modifications of locals withinexecdon't change the locals of the function is a documented difference with python2 (the documentation says "Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns"), and I prefer the behaviour of python2. - If a variable is set within
execand this variable did not exist before, thenexecmodifies the internal dictionary unless the variable is set afterwards. It seems that there is a bug in the waylocals()updates the dictionary ; this bug gives access to the value set withinexecby callinglocals()afterexec.