Python: How to Run Eval() in the Local Scope of a Function

Python: How can I run eval() in the local scope of a function

Save the result of locals() (or vars()) call to return the function's local scope. Otherwise, locals() inside the generator expression will return the gen-expr's local scope.

def test3():
lvar1 = 1
lvar2 = 2
lvar3 = 3
scope = locals()
myDict = dict((name, eval(name, scope)) for name in [
"lvar1", "lvar2", "lvar3"])
print(myDict["lvar1"])

BTW, you don't need an explicit comprehension to build that dict:

# copy() avoids quirky, unexpected updates if something else (like a debugger)
# accesses locals() or f_locals
myDict = locals().copy() # or vars().copy()

Python: How to let eval() see local variables?

This is a super subtle point. So, if you read the documentation for eval, it doesn't mention the case where you provide arguments for both globals and locals, but I am fairly certain it works the same as for exec:

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

In class definitions, functions don't get access to their enclosing scope. So this is exactly the same as the error:

>>> class Foo:
... value = [1,2,3]
... print([x in value for x in [2,4,6]])
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in Foo
File "<stdin>", line 3, in <listcomp>
NameError: name 'value' is not defined

Because list comprehensions work by creating a function object underneath the hood. This is also why you need self.some_method to access the names of other methods defined in your class. More about the above in the excellent accepted answer here.

So it's the same as:

>>> def foo():
... x = 3
... return eval('(lambda: x + 1)()', globals(), locals())
...
>>> foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in foo
File "<string>", line 1, in <module>
File "<string>", line 1, in <lambda>
NameError: name 'x' is not defined

However, this works just fine:

>>> def foo():
... x = 3
... return eval('x + 1', globals(), locals())
...
>>> foo()
4

Because there is no (non)enclosed function scope involved.

Finally, the reason that the following works:

>>> def foo():
... values = [1,2,3]
... return eval('[x+2 for x in values]', globals(), locals())
...
>>> foo()
[3, 4, 5]

Is because the iterable in the left-most for-clause of a comprehension gets evaluated not in the function scope of the comprehension but in the scope of where the comprehension occurs (it is literally passed as an argument). You can see this in the dissasembly of a list comprehension:

>>> import dis
>>> dis.dis('[x+2 for x in values]')
1 0 LOAD_CONST 0 (<code object <listcomp> at 0x7fe28baee3a0, file "<dis>", line 1>)
2 LOAD_CONST 1 ('<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_NAME 0 (values)
8 GET_ITER
10 CALL_FUNCTION 1
12 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x7fe28baee3a0, file "<dis>", line 1>:
1 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (x)
8 LOAD_FAST 1 (x)
10 LOAD_CONST 0 (2)
12 BINARY_ADD
14 LIST_APPEND 2
16 JUMP_ABSOLUTE 4
>> 18 RETURN_VALUE

Note, values is evaluated, iter is called on it, and the result of that is passed to the function:

              6 LOAD_NAME                0 (values)
8 GET_ITER
10 CALL_FUNCTION 1

The "function" is basically just a loop with append, see the: Disassembly of <code object <listcomp> at 0x7fe28baee3a0, file "<dis>", line 1> for how list comprehensions do their work.

Why can't eval find a variable defined in an outer function?

In short, since eval is for dynamic evaluation, the interpreter has no way to know it should add a to the local scope of g. For efficiency, the interpreter will not add unneeded variables to the dict of local variables.

From the doc for eval:

The expression argument is parsed and evaluated as a Python expression (technically speaking, a condition list) using the globals and locals dictionaries as global and local namespace.

This means the functions eval(expression) will use globals() as its default global scope and locals() as its local scope if none are provided.

Although, in you first example a is in neither.

def f(a):
print("f's locals:", locals())
def g():
print("g's locals:", locals())
print(eval('a'))
return g()

f(1)

Indeed, since the interpreter sees no reference to a when parsing the body of g, it does not add it to its local variables.

For it to work, you would need to specify nonlocal a in g.

Output

f's locals: {'a': 1}
g's locals: {}
Traceback ...
...
NameError: name 'a' is not defined

In your second example, a is in g local variables as it is used in the scope.

def f(a):
print("f's locals:", locals())
def g():
print("g's locals:", locals())
b = a + 1
print("g's locals after b = a + 1:", locals())
print(eval('a'))
return g()

f(1)

Output

f's locals: {'a': 1}
g's locals: {'a': 1}
g's locals after b = a + 1: {'a': 1, 'b': 2}
1

scope of eval function in python

Generators are implemented as function scopes:

The scope of names defined in a class block is limited to the class
block; it does not extend to the code blocks of methods – this
includes generator expressions since they are implemented using a
function scope
.

So, the generator inside the dict() constructor has its own locals() dictionary. Now let's take a look at Py_eval's source code, specially when both globals() and locals() are None:

if (globals == Py_None) {
globals = PyEval_GetGlobals();
if (locals == Py_None)
locals = PyEval_GetLocals();
}

So, for your example PyEval_GetLocals() will be empty at the moment the loop is executing and globals() will be the global dictionary. Note that i, j and k defined inside the function are not in local scope of generator, rather they are in its enclosing scope:

>>> dict((name,eval(name, globals(), {})) for name in ['i', 'j', 'k'])
{'i': 7, 'k': 10, 'j': 8}

Python:dict comprehension and eval function variable scope

To understand whats happening, try this:

def foo():
global one
one = '1'
two = '2'
print({'locals, global': (locals(), globals()) for _ in range(1)})

foo()

Output

{'locals, global': ({'_': 0, '.0': <range_iterator object at ...>},
{'__name__': '__main__', '__package__': None, ..., 'one': '1'})}

The builtin eval(expression) is a shortcut for eval(expression[, globals[, locals]]).

As you see in the previous output, locals() is not local symbol table of the function because list/dict comprehensions have their own scope (see https://bugs.python.org/msg348274 for instance).

To get the output you expected, you just have to pass the local symbol table of the function to eval.

def bar():
one = '1'
two = '2'
three = '3'
func_locals = locals() # bind the locals() here
print({name: eval(name, globals(), func_locals) for name in ('one', 'two', 'three')})

bar()

Output

{'one': '1', 'two': '2', 'three': '3'}

Scope within eval in list comprehension with method

My understanding of scope in python 2 (I have just moved to python 3) was that a should not be defined within run(), but a2 is.

Both a and a2 are visible from within run. a is defined at the global scope, so it is visible everywhere in that file.

I expected that if test5 runs OK then test6 and test7 should also be fine.

In 3.X, list comprehensions get their own scope. The test6 list comprehension has access to three scopes: the scope of the list comprehension, the scope of the function, and the global scope. So it has access to i and a2 and a.

By default, code executed inside eval has access to two scopes: the global scope and the closest local scope. This means the test7 eval can access variables defined at the file level and it can access variables defined inside the list comprehension, but it can't access variables defined inside the function but outside of the list comprehension. It can see a and i but not a2.

In 2.7, list comprehensions do not get their own scope. They share the same scope as the function that they're defined in. This explains why your code executes in 2.7 but not in 3.X. IIRC, this is the only change to the scope system between 2.7 and 3.X. (and if it isn't, it's the only change that's relevant to this scenario.)

Python lambda and eval scoping within function

So, this is clearly documented:

Note, eval() does not have access to the nested
scopes
(non-locals) in the enclosing environment.

So, you have the local scope of f and the nonlocal scope of foo, which eval will not have access to. You will have to capture the non-local scope and force eval to use it. How exactly you make this work is up to you and your use-case, here is one example:

>>> def foo(q, s):
... a = 1
... nonlocals = locals()
... def f(x):
... return eval(s, {**locals(), **nonlocals})
... return f(1)
...
>>> foo(1, "x+a+q")
3

Some things to note:

  1. I used the locals() of the function f first. You could switch the order if you want, and it won't matter if and only if there would be no name collisions. Otherwise it is up to you which takes precedence
  2. In the more recent versions of Python, {**locals(), **nonlocals} could be locals() | nonlocals
  3. I didn't use a lambda expression here, to comport with PEP8 style guidelines. Don't assign the result of a lambda expression to a name, that defeats the entire purpose of lambda expressions, which are to be anonymous.
  4. You should consider whether you really need to use eval here. It is likely not the best solution to your problem.

How to use eval() function with a list of variables?

eval takes local variables as third argument(reference),
so you can do this:

from sympy import Symbol
zs = [Symbol('x'), Symbol('y')]
eval('x+y', None, dict([z.name, z] for z in zs))

However, maybe you should use parse_expr which is part of SymPy.

from sympy import Symbol
from sympy.parsing.sympy_parser import parse_expr
zs = [Symbol('x'), Symbol('y')]
parse_expr('x+y', local_dict=dict([z.name, z] for z in zs))


Related Topics



Leave a reply



Submit