K. Mao K. Mao - 29 days ago 4x
Python Question

Modules and variable scopes

I'm not an expert at python, so bear with me while I try to understand the nuances of variable scopes.

As a simple example that describes the problem I'm facing, say I have the following three files.

The first file is outside_code.py. Due to certain restrictions I cannot modify this file. It must be taken as is. It contains some code that runs an eval at some point (yes, I know that eval is the spawn of satan but that's a discussion for a later day). For example, let's say that it contains the following lines of code:

def eval_string(x):
return eval(x)

The second file is a set of user defined functions. Let's call it functions.py. It contains some unknown number of function definitions, for example, let's say that functions.py contains one function, defined below:

def foo(x):
print("Your number is {}!".format(x))

Now I write a third file, let's call it main.py. Which contains the following code:

import outside_code
from functions import *

I import all of the function definitions from functions.py with a *, so they should be accessible by main.py without needing to do something like functions.foo(). I also import outside_code.py so I can access its core functionality, the code that contains an eval. Finally I call the function in outside_code.py, passing a string that is related to a function defined in functions.py.

In the simplified example, I want the code to print out "Your number is 4!". However, I get an error stating that 'foo' is not defined. This obviously means that the code in outside_code.py cannot access the same foo function that exists in main.py. So somehow I need to make foo accessible to it. Could anyone tell me exactly what the scope of foo currently is, and how I could extend it to cover the space that I actually want to use it in? What is the best way to solve my problem?


You'd have to add those names to the scope of outside_code. If outside_code is a regular Python module, you can do so directly:

import outside_code
import functions

for name in getattr(functions, '__all__', (n for n in vars(functions) if not n[0] == '_')):
    setattr(outside_code, name, getattr(functions, name))

This takes all names functions exports (which you'd import with from functions import *) and adds a reference to the corresponding object to outside_code so that eval() inside outside_code.eval_string() can find them.

You could use the ast.parse() function to produce a parse tree from the expression before passing it to eval_function() and then extract all global names from the expression and only add those names to outside_code to limit the damage, so to speak, but you'd still be clobbering the other module namespace to make this work.

Mind you, this is almost as evil as using eval() in the first place, but it's your only choice if you can't tell eval() in that other module to take a namespace parameter. That's because by default, eval() uses the global namespace of the module it is run in as the namespace.

If, however, your eval_string() function actually accepts more parameters, look for a namespace or globals option. If that exists, the function probably looks more like this:

def eval_string(x, namespace=None):
    return eval(x, globals=namespace)

after which you could just do:

outside_code.eval_string('foo(4)', vars(functions))

where vars(functions) gives you the namespace of the functions module.