Reputation: 92
I have the following:
x = [1,2,3,4,5]
def foo(lbd:str, value):
ret_val = eval(lbd, globals(), locals())
print(ret_val)
using 'value' variable in this call succeeds:
>>> foo("[i for i in value]",x)
[1, 2, 3, 4, 5]
but this one fails:
>>> foo(r"any([x in value for x in {'',0,None,'0'}])", x)
Traceback (most recent call last):
File "<pyshell#171>", line 1, in <module>
foo(r"any([x in value for x in {'',0,None,'0'}])", x)
File "<pyshell#165>", line 2, in foo
ret_val = eval(lbd, globals(), locals())
File "<string>", line 1, in <module>
File "<string>", line 1, in <listcomp>
NameError: name 'value' is not defined
I am able to work around this, but curious to know what's going on here.
>>> foo(r"(lambda V=value: any([x in V for x in {'',0,None,'0'}]) )()", x)
False
Upvotes: 3
Views: 1390
Reputation: 95872
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.
Upvotes: 8
Reputation: 4439
In addition to @juanpa.arrivillaga's answer, there is some discussion of this behavior in this bug report (it's not a bug).
Here's a quick fix for your immediate question:
x = [1,2,3,4,5]
def foo(lbd:str, value):
ret_val = eval(lbd, {'value': value})
print(ret_val)
>>> foo(r"any([x in value for x in {'',0,None,'0'}])", x)
False
Upvotes: 4