Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are module-level variables in a Python exec inaccessible?

Tags:

python

I'm trying to use Python's exec in a project to execute embedded Python code.

The problem I've encountered is that variables created at the module-level in an exec statement are inaccessible from functions defined in the same module.

Let's say you have the following Python program:

x = 5
def foo():
    print x
foo()

If you put the above four lines in a file and ran it, it would work no problemo.

However, if you try running this same piece of code from within an exec statement, it won't work.

Here's our previous program, inside an exec statement:

import __builtin__

global_env = {'__builtins__': __builtin__}
local_env = dict()

exec """
x = 5
def foo():
    print x
foo()
""" in global_env, local_env

On execution, instead of working, it yields the following error:

Traceback (most recent call last):
  File "lab.py", line 94, in <module>
    """ in global_env, local_env
  File "<string>", line 5, in <module>
  File "<string>", line 4, in foo
NameError: global name 'x' is not defined

I thought module-level variables were stored globally, but it seems that, at least in exec, they're not.

For example, in the previous example, if you replace the call to foo() with:

print global_env
print local_env

You get:

{'__builtins__': <module '__builtin__' (built-in)>}
{'x': 5, 'foo': <function foo at 0x102c12938>}

So anything defined in the module-level (including x) is stored in locals().

But it's impossible to access x from anywhere except the module-level (of the exec statement). In particular, as we saw above, the local scope of x is invisible to functions defined in the same exec statement.

Workarounds

I've found two ways to workaround this issue, and make x accessible again.

The first one is using the global keyword in the function:

exec """
x = 5
def foo():
    global x
    print x
foo()
""" in global_env, local_env

The second one is using the same dictionary for globals() and locals() in exec:

exec """
x = 5
def foo():
    print x
foo()
""" in global_env

However, these are just half-fixes/workarounds that don't address the original issue.

So my questions are: Why are module-level variables in an exec stored locally, and why are inaccessible from anywhere but the module-level?

Some closely related StackOverflow posts:

  • globals and locals in python exec()
  • Cannot change global variables in a function through an exec() statement?
like image 257
Arjun Menon Avatar asked Jul 12 '14 10:07

Arjun Menon


People also ask

Can Python modules have variables?

Each Module/file has its own global variable Every command in this Python file can access, read, and modify the value of the variable, that is, it becomes a global variable. You can also explicitly make a variable defined within the functions globally by declaring it global.

What are module level variables?

Module-level variables can be either public or private. Public variables are available to all procedures in all modules in a project; private variables are available only to procedures in that module. By default, variables declared with the Dim statement in the Declarations section are scoped as private.

Why should global variables be avoided in Python?

While in many or most other programming languages variables are treated as global if not declared otherwise, Python deals with variables the other way around. They are local, if not otherwise declared. The driving reason behind this approach is that global variables are generally bad practice and should be avoided.


1 Answers

To understand what is going on, you need to read the docs very closely. The key part says:

If two separate objects are given as globals and locals, the code will be executed as if it were embedded in a class definition.

This means that local assignments get made into the local namespace (equivalent to class-level variables), but functions (i.e. methods) do not become closures if they try to reference a local (class) variable.

Compare your code with:

class Test(object):
    x = 1
    def foo():
        print x
    foo()

You'll get the same error, for the same reason. foo is not a closure, and so it attempts to reference x in the global namespace (unsuccessfully).

like image 187
Blckknght Avatar answered Oct 20 '22 20:10

Blckknght