python - eval、exec 和 compile 之间有什么区别?

标签 python dynamic eval exec

我一直在研究 Python 代码的动态评估,并遇到了 eval()compile()函数,以及 exec陈述。

有人可以解释一下 eval 之间的区别吗?和 exec ,以及compile()的不同模式如何适合?

最佳答案

简短的回答,或 TL;DR

基本上, eval 习惯于评估 使用单个动态生成的 Python 表达式,以及 exec 习惯于执行 ute 动态生成的 Python 代码只是为了它的副作用。
evalexec有这两个区别:

  • eval只接受 单表达式 , exec可以采用包含 Python 语句的代码块:循环,try: except: , class和函数/方法def观点等。

    Python 中的表达式是您可以在变量赋值中使用的任何值:
    a_variable = (anything you can put within these parentheses is an expression)
    
  • eval 返回值 给定表达式的,而 exec忽略其代码的返回值,并始终返回 None (在 Python 2 中,它是一个语句,不能用作表达式,因此它实际上不返回任何内容)。

  • 在 1.0 - 2.7 版本中,exec是一个声明,因为 CPython 需要为使用 exec 的函数生成一种不同类型的代码对象。因为它在函数内部的副作用。

    在 Python 3 中,exec是一个函数;它的使用对使用它的函数的编译字节码没有影响。

    因此基本上:
    >>> a = 5
    >>> eval('37 + a')   # it is an expression
    42
    >>> exec('37 + a')   # it is an expression statement; value is ignored (None is returned)
    >>> exec('a = 47')   # modify a global variable as a side effect
    >>> a
    47
    >>> eval('a = 47')  # you cannot evaluate a statement
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 1
        a = 47
          ^
    SyntaxError: invalid syntax
    
    compile'exec' mode 将任意数量的语句编译成一个字节码,该字节码总是隐式返回 None ,而在 'eval'模式它将单个表达式编译成字节码,返回该表达式的值。
    >>> eval(compile('42', '<string>', 'exec'))  # code returns None
    >>> eval(compile('42', '<string>', 'eval'))  # code returns 42
    42
    >>> exec(compile('42', '<string>', 'eval'))  # code returns 42,
    >>>                                          # but ignored by exec
    

    'eval'模式(如果传入字符串,则使用 eval 函数),compile如果源代码包含语句或除单个表达式之外的任何其他内容,则引发异常:
    >>> compile('for i in range(3): print(i)', '<string>', 'eval')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 1
        for i in range(3): print(i)
          ^
    SyntaxError: invalid syntax
    

    实际上,语句“eval 只接受单个表达式”仅适用于将字符串(包含 Python 源代码)传递给 eval 的情况。 .然后使用 compile(source, '<string>', 'eval') 在内部编译为字节码这就是差异的真正来源。

    如果 code对象(包含 Python 字节码)被传递给 execeval ,它们的行为相同,除了 exec忽略返回值,仍然返回 None总是。所以可以使用 eval执行有语句的东西,如果你只是 compile d 之前将其转换为字节码,而不是将其作为字符串传递:
    >>> eval(compile('if 1: print("Hello")', '<string>', 'exec'))
    Hello
    >>>
    

    即使编译后的代码包含语句,也可以正常工作。它仍然返回 None ,因为那是从 compile 返回的代码对象的返回值.

    'eval'模式(如果传入字符串,则使用 eval 函数),compile如果源代码包含语句或除单个表达式之外的任何其他内容,则引发异常:
    >>> compile('for i in range(3): print(i)', '<string>'. 'eval')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 1
        for i in range(3): print(i)
          ^
    SyntaxError: invalid syntax
    

    更长的答案,也就是血腥的细节
    execeval
    exec 函数(它是 a statement in Python 2 )用于执行动态创建的语句或程序:
    >>> program = '''
    for i in range(3):
        print("Python is cool")
    '''
    >>> exec(program)
    Python is cool
    Python is cool
    Python is cool
    >>> 
    

    eval 函数对 single expression 执行相同的操作,并返回表达式的值:
    >>> a = 2
    >>> my_calculation = '42 * a'
    >>> result = eval(my_calculation)
    >>> result
    84
    
    execeval两者都接受要作为 str 运行的程序/表达式, unicodebytes包含源代码的对象,或作为 code包含 Python 字节码的对象。

    如果 str/unicode/bytes包含源代码已传递给 exec ,它的行为等效于:
    exec(compile(source, '<string>', 'exec'))
    

    eval类似的行为相当于:
    eval(compile(source, '<string>', 'eval'))
    

    由于所有表达式都可以用作 Python 中的语句(这些在 Python 中称为 Expr 节点 abstract grammar ;反之则不然),您始终可以使用 exec如果您不需要返回值。也就是说,您可以使用 eval('my_func(42)')exec('my_func(42)') ,不同之处在于 eval返回 my_func 返回的值, 和 exec丢弃它:
    >>> def my_func(arg):
    ...     print("Called with %d" % arg)
    ...     return arg * 2
    ... 
    >>> exec('my_func(42)')
    Called with 42
    >>> eval('my_func(42)')
    Called with 42
    84
    >>> 
    

    2,只有exec接受包含语句的源代码,如 def , for , while , import , 或 class 、赋值语句(又名 a = 42)或整个程序:
    >>> exec('for i in range(3): print(i)')
    0
    1
    2
    >>> eval('for i in range(3): print(i)')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 1
        for i in range(3): print(i)
          ^
    SyntaxError: invalid syntax
    

    两者 execeval接受 2 个额外的位置参数 - globalslocals - 代码看到的全局和局部变量范围。这些默认为 globals()locals()在调用 exec 的范围内或 eval ,但任何字典都可以用于 globals和任何 mappinglocals (当然包括 dict)。这些不仅可以用于限制/修改代码看到的变量,而且还经常用于捕获 exec 的变量。 ted 代码创建:
    >>> g = dict()
    >>> l = dict()
    >>> exec('global a; a, b = 123, 42', g, l)
    >>> g['a']
    123
    >>> l
    {'b': 42}
    

    (如果您显示整个 g 的值,它会更长,因为 execeval 将内置模块作为 __builtins__ 添加到全局变量中(如果缺少)。

    在 Python 2 中,exec 的官方语法声明实际上是exec code in globals, locals ,如
    >>> exec 'global a; a, b = 123, 42' in g, l
    

    然而,替代语法 exec(code, globals, locals)也一直被接受(见下文)。
    compile
    compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1) 内置可用于加速相同代码的重复调用 execeval通过将源编译为 code事先对象。 mode参数控制代码片段的种类compile函数接受以及它产生的字节码类型。选项是'eval' , 'exec''single' :
  • 'eval' mode 需要一个单一的表达式,并且会产生字节码,运行时会返回 的值。那个表情 :
    >>> dis.dis(compile('a + b', '<string>', 'eval'))
      1           0 LOAD_NAME                0 (a)
                  3 LOAD_NAME                1 (b)
                  6 BINARY_ADD
                  7 RETURN_VALUE
    
  • 'exec'接受从单个表达式到整个代码模块的任何类型的 Python 构造,并像执行模块顶级语句一样执行它们。代码对象返回 None :
    >>> dis.dis(compile('a + b', '<string>', 'exec'))
      1           0 LOAD_NAME                0 (a)
                  3 LOAD_NAME                1 (b)
                  6 BINARY_ADD
                  7 POP_TOP                             <- discard result
                  8 LOAD_CONST               0 (None)   <- load None on stack
                 11 RETURN_VALUE                        <- return top of stack
    
  • 'single''exec'的限定形式它接受包含 的源代码单例语句(或由 ; 分隔的多个语句)如果最后一个语句是表达式语句,则生成的字节码也会打印 repr该表达式的值到标准输出(!)。

    if - elif - else链,一个带有 else 的循环, 和 try与其 except , elsefinally块被认为是单个语句。

    包含 2 个顶级语句的源代码片段是 'single' 的错误,除了在 Python 2 中有一个 bug,有时允许在代码中使用多个顶级语句;只编译第一个;其余的被忽略:

    在 Python 2.7.8 中:
    >>> exec(compile('a = 5\na = 6', '<string>', 'single'))
    >>> a
    5
    

    在 Python 3.4.2 中:
    >>> exec(compile('a = 5\na = 6', '<string>', 'single'))
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 1
        a = 5
            ^
    SyntaxError: multiple statements found while compiling a single statement
    

    这对于制作交互式 Python shell 非常有用。但是,即使您 eval,表达式的值也不会返回。结果代码。

  • 因此最大的区别execeval实际上来自compile功能及其模式。

    除了将源代码编译为字节码之外,compile支持编译abstract syntax trees (解析 Python 代码树)到 code对象;并将源代码转换为抽象语法树(ast.parse 是用 Python 编写的,只需调用 compile(source, filename, mode, PyCF_ONLY_AST) );例如,这些用于动态修改源代码,也用于动态代码创建,因为在复杂情况下,将代码作为节点树而不是文本行处理通常更容易。

    虽然 eval只允许您评估包含单个表达式的字符串,您可以 eval一个完整的语句,甚至是一个完整的模块,已经 compile d 成字节码;也就是说,使用 Python 2,print是声明,不能是 eval直接领导:
    >>> eval('for i in range(3): print("Python is cool")')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 1
        for i in range(3): print("Python is cool")
          ^
    SyntaxError: invalid syntax
    
    compile它与 'exec'模式进入 code对象,你可以 eval ; eval函数将返回 None .
    >>> code = compile('for i in range(3): print("Python is cool")',
                       'foo.py', 'exec')
    >>> eval(code)
    Python is cool
    Python is cool
    Python is cool
    

    如果查看 eval exec CPython 3 中的源代码,这一点非常明显;他们都打电话PyEval_EvalCode使用相同的参数,唯一的区别是 exec explicitly returns None .
    exec 的语法差异在 Python 2 和 Python 3 之间

    Python 的主要区别之一 2 exec是声明和 eval是一个内置函数(两者都是 Python 3 中的内置函数)。exec的官方语法是众所周知的事实。在 Python 2 中是 exec code [in globals[, locals]] .

    不像大多数 Python 2-to-3 porting guides seem to suggest , exec CPython 2 中的语句也可以与看起来 的语法一起使用正好喜欢 exec Python 3 中的函数调用。原因是 Python 0.9.9 有 exec(code, globals, locals)内置功能!并且该内置函数被替换为 exec声明somewhere before Python 1.0 release .

    由于希望不破坏与 Python 0.9.9 的向后兼容性,Guido van Rossum added a compatibility hack in 1993 : 如果 code是一个长度为 2 或 3 的元组,并且 globalslocals没有传入exec否则声明,code将被解释为好像元组的第二个和第三个元素是 globalslocals分别。即使在 Python 1.4 documentation (the earliest available version online) 中也没有提到兼容性黑客。 ;因此,许多移植指南和工具的作者都不知道,直到 documented再次in November 2012 :

    The first expression may also be a tuple of length 2 or 3. In this case, the optional parts must be omitted. The form exec(expr, globals) is equivalent to exec expr in globals, while the form exec(expr, globals, locals) is equivalent to exec expr in globals, locals. The tuple form of exec provides compatibility with Python 3, where exec is a function rather than a statement.



    是的,在 CPython 2.7 中,它被方便地称为向前兼容选项(为什么人们会混淆根本没有向后兼容选项),
    当它实际上已经存在了二十年的向后兼容性时。

    因此,虽然 exec是 Python 1 和 Python 2 中的语句,以及 Python 3 和 Python 0.9.9 中的内置函数,
    >>> exec("print(a)", globals(), {'a': 42})
    42
    

    可能在每个广泛发布的 Python 版本中都有相同的行为;并且在 Jython 2.5.2、PyPy 2.3.1 (Python 2.7.6) 和 IronPython 2.6.1 中也能工作(感谢他们密切关注 CPython 的未记录行为)。

    你不能在 Pythons 1.0 - 2.7 中使用它的兼容性黑客做的是存储 exec 的返回值变成一个变量:
    Python 2.7.11+ (default, Apr 17 2016, 14:00:29) 
    [GCC 5.3.1 20160413] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> a = exec('print(42)')
      File "<stdin>", line 1
        a = exec('print(42)')
               ^
    SyntaxError: invalid syntax
    

    (这在 Python 3 中也没有用,因为 exec 总是返回 None ),或者传递对 exec 的引用:
    >>> call_later(exec, 'print(42)', delay=1000)
      File "<stdin>", line 1
        call_later(exec, 'print(42)', delay=1000)
                      ^
    SyntaxError: invalid syntax
    

    某人可能实际使用过的模式,尽管不太可能;

    或者在列表理解中使用它:
    >>> [exec(i) for i in ['print(42)', 'print(foo)']
      File "<stdin>", line 1
        [exec(i) for i in ['print(42)', 'print(foo)']
            ^
    SyntaxError: invalid syntax
    

    这是滥用列表理解(使用 for 循环代替!)。

    关于python - eval、exec 和 compile 之间有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2220699/

    相关文章:

    Python Matplotlib : Centering figure around a moving artist

    python - 将数据框中的列值拆分为空列值

    java - 生成用于在 Java 中配置对象属性的可视化界面

    Linux 找不到动态链接的应用程序

    perl - 为什么在没有 block 的情况下调用 eval 会终止这个 Perl 程序?

    javascript - 如何将参数传递给 eval() javascript 中的字符串?

    python - 如何使循环中的类具有相同的属性

    python - Nesterov 的加速梯度下降是如何在 Tensorflow 中实现的?

    java - 未重写的公共(public)实例方法是否动态绑定(bind)?

    javascript - 如何在JavaScript中不写 "eval"就执行 "eval"