I thought this would print 3, but it prints 1:
def f():
a = 1
exec(\"a = 3\")
print(a)
The reason that you can't change local variables within a function using exec in that way, and why exec acts the way it does, can be summarized as following:
exec is a function that shares its local scope with the scope of the most inner scope in which it's called. local() dictionary. When you define a new object in exec what it does is roughly equivalent to following:from copy import copy
class exec_type:
def __init__(self, *args, **kwargs):
# default initializations
# ...
self.temp = copy(locals())
def __setitem__(self, key, value):
if var not in locals():
set_local(key, value)
self.temp[key] = value
temp is a temporary namespace that resets after each instantiation (each time you call the exec).
A more comprehensive example would be something like following:
g_var = 5
def test():
l_var = 10
print(locals())
exec("print(locals())")
exec("g_var = 222")
exec("l_var = 111")
exec("print(locals())")
exec("l_var = 111; print(locals())")
exec("print(locals())")
print(locals())
def inner():
exec("print(locals())")
exec("inner_var = 100")
exec("print(locals())")
exec("print([i for i in globals() if '__' not in i])")
print("Inner function: ")
inner()
print("-------" * 3)
return (g_var, l_var)
print(test())
exec("print(g_var)")
Output:
{'l_var': 10}
{'l_var': 10}
locals are the same.
{'l_var': 10, 'g_var': 222}
after adding g_var and changing the l_var it only adds g_var and left the l_var unchanged.
{'l_var': 111, 'g_var': 222}
l_var is changed because we are changing and printing the locals in one instantiation ( one call to exec).
{'l_var': 10, 'g_var': 222}
{'l_var': 10, 'g_var': 222}
In both function's locals and exec's local l_var is unchanged and g_var is added.
Inner function:
{}
{'inner_var': 100}
{'inner_var': 100}
inner_function's local is same as exec's local.
['g_var', 'test']
global is only contain g_var and function name (after excluding the special methods).
---------------------
(5, 10)
5