【问题标题】:Python: How to let eval() see local variables?Python:如何让 eval() 查看局部变量?
【发布时间】:2021-08-27 21:52:04
【问题描述】:

我有以下几点:

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

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

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

【问题讨论】:

    标签: python python-3.x eval local-variables


    【解决方案1】:

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

    如果exec 获得两个单独的对象作为全局对象和局部对象,则代码将 就像嵌入在类定义中一样执行。

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

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

    是因为理解的 left-most for 子句中的迭代不是在理解的函数范围内而是在理解发生的范围内进行评估的(它实际上被传递为一个论点)。您可以在列表理解的反汇编中看到这一点:

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

    “函数”基本上只是一个带有追加的循环,请参阅:Disassembly of &lt;code object &lt;listcomp&gt; at 0x7fe28baee3a0, file "&lt;dis&gt;", line 1&gt; 了解列表推导如何工作。

    【讨论】:

      【解决方案2】:

      除了@juanpa.arrivillaga 的answerthis bug report 中也有一些关于这种行为的讨论(这不是错误)。

      这里是您直接问题的快速解决方案:

      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
      

      【讨论】:

      • 嗯,这在这里有效,但大概,OP 想要 globals 中的所有内容。可能,解决方案类似于eval(lbd, {"value":value, **globals})
      • 添加全局变量很有帮助,但我只是在寻找“价值”
      猜你喜欢
      • 1970-01-01
      • 2019-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-09
      • 1970-01-01
      相关资源
      最近更新 更多