Exec() and Variable Scope

Why does the exec() function work differently inside and outside of a function?

Pass the current local scope to the function and use that as the local dictionary for exec().

# example 2
def example_function(loc):
string_two = 'greeting_two = "hi"'
exec(string_two, loc)
print(greeting_two)

example_function(locals())

Read more here

TSQL: Variable scope and EXEC()

cmsjr has a point about not needing exec for this. But assuming you're posting a simplified version of the problem and exec is a requirement:

There's no column named "value" because there's no table. If you just want it to print value, you need to encase it in quotes so it looks like a string literal inside the call to exec:

exec(' select ''' + @test + ''' ')

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

Python variable scope for exec?

You need to add the name space for exec. Use the local name space (exec strr in locals()) and it works:

def f2():
x = []
strr = "def g():\n x.append(1)\n"
exec strr in locals()
locals()["g"]()
print x

>>> f2()
[1]

scope of globals, locals, use with exec()

In your first example, locals is the globals dict, because you're on a module level.

You can check that with print(id(globals()), id(locals()))

In your second example, locals is a different dict in the scope of the function.

So to make example 2 work, either use:

def f1(glob, loc):
exec("b = 5", glob, loc)
def f2():
f1(globals(), globals())
print(b + 1)
f2()

or

def f1(glob, loc):
exec("b = 5", glob, glob)
def f2(loc):
f1(globals(), loc)
print(b + 1)
f2(locals())

So this isn't really a matter of exec() but of in which scope you're calling locals().

And ofc it's also a matter of what the code in exec does, because an assignment changes the scope of the name to the current (local) scope, unless global/nonlocal is involved. That's why b=5 is executed in the local scope - except that in your first example, the "local" scope is the global scope.


Follow-up:

if you want to re-assign a value to b in f2 you will need the global keyword:

def f1(glob, loc):
exec("b = 5", glob, loc)
def f2():
f1(globals(), globals())
global b
b += 1
print(b + 1) # 7
f2()

Note that this has nothing to do with exec and passing around locals() / globals() - you can reduce it to this example, which would also fail w/o the global keyword (it would throw an UnboundLocalError: local variable 'b' referenced before assignment error).

b = 5
def f2():
global b
b += 1
print(b + 1) # 7
f2()

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. ...

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.

exec has different side effects in different contexts?

Sadly from my understanding, you cannot use exec like that in a function. Instead you can store this in a dictionary as shown below.

def demo():
varDict = {}
exec("b=2", varDict)
print(varDict["b"])
demo()

output

2

globals and locals in python exec()

Well, I believe it's either an implementation bug or an undocumented design decision. The crux of the issue is that a name-binding operation in the module-scope should bind to a global variable. The way it is achieved is that when in the module level, globals() IS locals() (try that one out in the interpreter), so when you do any name-binding, it assigns it, as usual, to the locals() dictionary, which is also the globals, hence a global variable is created.

When you look up a variable, you first check your current locals, and if the name is not found, you recursively check locals of containing scopes for the variable name until you find the variable or reach the module-scope. If you reach that, you check the globals, which are supposed to be the module scope's locals.

>>> exec(compile("import sys\nprint sys._getframe().f_code.co_name", "blah", "exec"), {}, {})
<module>
>>> exec("a = 1\nclass A(object):\n\tprint a\n", {}, {})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 2, in <module>
File "<string>", line 3, in A
NameError: name 'a' is not defined
>>> d = {}
>>> exec("a = 1\nclass A(object):\n\tprint a\n", d,d)
1

This behavior is why inheritance worked (The name-lookup used code object's scope locals(), which indeed had A in it).

In the end, it's an ugly hack in the CPython implementation, special-casing globals lookup. It also causes some nonsensical artifical situations - e.g.:

>>> def f():
... global a
... a = 1
...
>>> f()
>>> 'a' in locals()
True

Please note that this is all my inference based on messing with the interpreter while reading section 4.1 (Naming and binding) of the python language reference. While this isn't definitive (I haven't opened CPython's sources), I'm fairly sure I'm correct about the behavior.



Related Topics



Leave a reply



Submit