Python:如何让eval()看到局部变量?

2024-10-17 08:34:01 发布

您现在位置:Python中文网/ 问答频道 /正文

我有以下资料:

x = [1,2,3,4,5]
def foo(lbd:str, value):
    ret_val = eval(lbd, globals(), locals())
    print(ret_val)

在此调用中使用“value”变量成功:

>>> foo("[i for i in value]",x)
            
[1, 2, 3, 4, 5]

但这一次失败了:

>>> 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

我能够解决这个问题,但很想知道这里发生了什么

>>> foo(r"(lambda V=value: any([x in V for x in {'',0,None,'0'}]) )()", x)
False

Tags: innoneforfoovalueevallineany
2条回答

这是一个非常微妙的观点。因此,如果您阅读documentation for ^{},它没有提到为全局变量和局部变量提供参数的情况,但我相当确定它的工作原理与for ^{}相同:

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

在类定义中,函数无法访问其封闭范围。因此,这与错误完全相同:

>>> 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

因为列表理解是通过在引擎盖下创建函数对象来工作的。这也是为什么需要self.some_method来访问类中定义的其他方法的名称。有关上述内容的更多信息,请参见the excellent accepted answer here

因此,这与:

>>> 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

但是,这很管用:

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

因为不涉及(非)封闭的功能范围

最后,以下原因起作用:

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

这是因为理解的最左边的for子句中的iterable不是在理解的函数范围内计算的,而是在理解发生的范围内计算的(它实际上是作为参数传递的)。您可以在列表的分解中看到这一点:

>>> 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

注意,values被计算,对其调用iter,其结果被传递给函数:

              6 LOAD_NAME                0 (values)
              8 GET_ITER
             10 CALL_FUNCTION            1

“函数”基本上只是一个带有append的循环,有关列表理解是如何工作的,请参见:Disassembly of <code object <listcomp> at 0x7fe28baee3a0, file "<dis>", line 1>

除了@juanpa.arrivillaga的answer之外,在this bug report中还讨论了这种行为(它不是一个bug)

这里有一个快速解决您眼前问题的方法:

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

相关问题 更多 >