0

I have the following code

def foo(q, s):
    a = 1
    f = lambda x : eval(s)
    return f(1)

foo(1, "x+a+q")

I would expect the function call to return 3, however I get a NameError instead. I assume that's because eval doesn't have access to the local scope. However I wasn't able to make it work even after passing locals() or a dictionary with the objects.

apocalypsis
  • 520
  • 8
  • 19

1 Answers1

2

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.
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
  • thank you! I don't really like `eval` either, though I need to read a function definition from an Excel cell using `xlwings` so didn't really see another way – apocalypsis Jul 04 '22 at 09:38