How to Get Local Variables Updated, When Using the 'Exec' Call

How to get local variables updated, when using the `exec` call?

This issue is somewhat discussed in the Python3 bug list. Ultimately, to get this behavior, you need to do:

def foo():
ldict = {}
exec("a=3",globals(),ldict)
a = ldict['a']
print(a)

And if you check the Python3 documentation on exec, you'll see the following 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 function exec() returns.

That means that one-argument exec can't safely perform any operations that would bind local variables, including variable assignment, imports, function definitions, class definitions, etc. It can assign to globals if it uses a global declaration, but not locals.

Referring back to a specific message on the bug report, Georg Brandl says:

To modify the locals of a function on the fly is not
possible without several consequences: normally, function locals are not
stored in a dictionary, but an array
, whose indices are determined at
compile time from the known locales. This collides at least with new
locals added by exec. The old exec statement circumvented this, because
the compiler knew that if an exec without globals/locals args occurred
in a function, that namespace would be "unoptimized", i.e. not using the
locals array. Since exec() is now a normal function, the compiler does
not know what "exec" may be bound to, and therefore can not treat is
specially
.

Emphasis is mine.

So the gist of it is that Python3 can better optimize the use of local variables by not allowing this behavior by default.

And for the sake of completeness, as mentioned in the comments above, this does work as expected in Python 2.X:

Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41) 
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def f():
... a = 1
... exec "a=3"
... print a
...
>>> f()
3

Setting variables with exec inside a function

You're almost there. You're trying to modify a global variable so you have to add the global statement:

old_string = "didn't work"
new_string = "worked"

def function():
exec("global old_string; old_string = new_string")
print(old_string)

function()

If you run the following version, you'll see what happened in your version:

old_string = "didn't work"
new_string = "worked"

def function():
_locals = locals()
exec("old_string = new_string", globals(), _locals)
print(old_string)
print(_locals)

function()

output:

didn't work
{'old_string': 'worked'}

The way you ran it, you ended up trying to modify the function's local variables in exec, which is basically undefined behavior. See the warning in the exec docs:

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 function exec() returns.

and the related warning on locals():

Note: The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.

How pass a value to a variable in python function with using exec()?

Python knows several kinds of scope: module global, function local, nonlocal closures, class body. Notably, scope resolution is defined statically at byte code compile time – most importantly, whether names refer to local/nonlocal or global scope cannot be changed.

Of these scopes, only global scope is guaranteed to behave similar to a dict, and as such writeable. The local/nonlocal scope is generally not writeable, and new variables cannot be added to it.

exec will write to the global scope if locals is not passed in; globals must then explicitly be set to its default of globals().

def func():
exec("a='exec'", globals()) # access only global scope
print(a)

a = 'global'
func() # prints exec

However, once a name is local to a function, exec cannot modify it.

def func():
a = 'local' # assignment makes name local
exec("a='exec global'", globals())
exec("a='exec locals'", globals(), locals())
print(a)

a = 'global'
func() # prints local

While a dict-like representation of local/nonlocal scope exists, the interpreter is not required to honour changes to it.

locals()

Update and return a dictionary representing the current local symbol table. Free variables are returned by locals() when it is called in function blocks, but not in class blocks. Note that at the module level, locals() and globals() are the same dictionary.

Note: The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.

Even though exec does take locals as a dict, these are not treated like function locals/nonlocals. Attempts to modify the default locals (the result of locals()) are not defined.

exec()

... If globals and locals are given, they are used for the global and local variables, respectively. If provided, locals can be any mapping object. Remember that at module level, globals and locals are the same dictionary. If exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition.

Note: The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. ...

Use exec to modify global variables in a class

exec() has optional arguments for you to provide the global and local variable contexts.

But you didn't provide them.



Related Topics



Leave a reply



Submit