eval、exec 和 compile 有什么区别?

2022-01-30 00:00:00 python eval exec dynamic

问题描述

我一直在研究 Python 代码的动态评估,遇到了 eval()compile() 函数,以及 exec语句.

I've been looking at dynamic evaluation of Python code, and come across the eval() and compile() functions, and the exec statement.

谁能解释一下 evalexec 之间的区别,以及 compile() 的不同模式如何适应?

Can someone please explain the difference between eval and exec, and how the different modes of compile() fit in?


解决方案

简答,或 TL;DR

基本上,使用 eval评估评估单个动态生成的 Python 表达式,并 exec 用于exec仅针对其副作用来exec使用动态生成的 Python 代码.

The short answer, or TL;DR

Basically, eval is used to evaluate a single dynamically generated Python expression, and exec is used to execute dynamically generated Python code only for its side effects.

evalexec 有这两个区别:

eval and exec have these two differences:

  1. eval 只接受 单个表达式,exec 可以接受包含 Python 语句的代码块:循环、try: except:, class 和函数/方法 definitions 等等.

  1. eval accepts only a single expression, exec can take a code block that has Python statements: loops, try: except:, class and function/method definitions and so on.

Python 中的表达式可以作为变量赋值中的任何值:

An expression in Python is whatever you can have as the value in a variable assignment:

a_variable = (anything you can put within these parentheses is an expression)

  • eval 返回给定表达式的值,而 exec 忽略其代码的返回值,并始终返回 None(在 Python 2 中它是一个语句,不能用作表达式,所以它真的不返回任何东西).

  • eval returns the value of the given expression, whereas exec ignores the return value from its code, and always returns None (in Python 2 it is a statement and cannot be used as an expression, so it really does not return anything).

    在 1.0 - 2.7 版本中,exec 是一个语句,因为 CPython 需要为使用 exec 的函数生成不同类型的代码对象,因为它在功能.

    In versions 1.0 - 2.7, exec was a statement, because CPython needed to produce a different kind of code object for functions that used exec for its side effects inside the function.

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

    In Python 3, exec is a function; its use has no effect on the compiled bytecode of the function where it is used.

    因此基本上:

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

    <小时>

    'exec' 模式下的 compile 将任意数量的语句编译成隐式总是返回 None 的字节码,而在 'eval' 模式将单个表达式编译成字节码,返回该表达式的值.


    The compile in 'exec' mode compiles any number of statements into a bytecode that implicitly always returns None, whereas in 'eval' mode it compiles a single expression into bytecode that returns the value of that expression.

    >>> 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 会引发异常如果源代码包含语句或超出单个表达式的任何其他内容:

    In the 'eval' mode (and thus with the eval function if a string is passed in), the compile raises an exception if the source code contains statements or anything else beyond a single expression:

    >>> 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') 这才是真正的区别所在.


    Actually the statement "eval accepts only a single expression" applies only when a string (which contains Python source code) is passed to eval. Then it is internally compiled to bytecode using compile(source, '<string>', 'eval') This is where the difference really comes from.

    如果将 code 对象(包含 Python bytecode)传递给 execeval,它们的行为相同,除了 exec 忽略返回值,仍然总是返回 None 的事实.所以可以使用 eval 来执行有语句的东西,如果你只是 compiled 之前把它变成字节码,而不是把它作为字符串传递:

    If a code object (which contains Python bytecode) is passed to exec or eval, they behave identically, excepting for the fact that exec ignores the return value, still returning None always. So it is possible use eval to execute something that has statements, if you just compiled it into bytecode before instead of passing it as a string:

    >>> eval(compile('if 1: print("Hello")', '<string>', 'exec'))
    Hello
    >>>
    

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

    works without problems, even though the compiled code contains statements. It still returns None, because that is the return value of the code object returned from compile.

    'eval' 模式下(如果传入字符串,则使用 eval 函数),compile 会引发异常如果源代码包含语句或超出单个表达式的任何其他内容:

    In the 'eval' mode (and thus with the eval function if a string is passed in), the compile raises an exception if the source code contains statements or anything else beyond a single expression:

    >>> 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 函数(其中Python 2 中的语句) 用于执行动态创建的语句或程序:

    The longer answer, a.k.a the gory details

    exec and eval

    The exec function (which was a statement in Python 2) is used for executing a dynamically created statement or program:

    >>> program = '''
    for i in range(3):
        print("Python is cool")
    '''
    >>> exec(program)
    Python is cool
    Python is cool
    Python is cool
    >>> 
    

    eval 函数执行单个表达式 相同,and 返回值表达式:

    The eval function does the same for a single expression, and returns the value of the expression:

    >>> a = 2
    >>> my_calculation = '42 * a'
    >>> result = eval(my_calculation)
    >>> result
    84
    

    execeval 都接受作为 strunicode 或包含源代码的 bytes 对象,或作为包含 Python 字节码的 code 对象.

    exec and eval both accept the program/expression to be run either as a str, unicode or bytes object containing source code, or as a code object which contains Python bytecode.

    如果将包含源代码的 str/unicode/bytes 传递给 exec,则其行为等同于:

    If a str/unicode/bytes containing source code was passed to exec, it behaves equivalently to:

    exec(compile(source, '<string>', 'exec'))
    

    eval 的行为类似地等同于:

    and eval similarly behaves equivalent to:

    eval(compile(source, '<string>', 'eval'))
    

    <小时>

    由于所有表达式都可以用作 Python 中的语句(这些在 Python 中称为 Expr 节点抽象语法;反之则不然),如果不需要返回值,可以随时使用exec.也就是说,你可以使用 eval('my_func(42)')exec('my_func(42)'),区别在于 eval 返回 my_func 返回的值,exec 将其丢弃:


    Since all expressions can be used as statements in Python (these are called the Expr nodes in the Python abstract grammar; the opposite is not true), you can always use exec if you do not need the return value. That is to say, you can use either eval('my_func(42)') or exec('my_func(42)'), the difference being that eval returns the value returned by my_func, and exec discards it:

    >>> 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 接受包含语句的源代码,例如 defforwhileimportclass、赋值语句(又名 a = 42)或整个程序:

    Of the 2, only exec accepts source code that contains statements, like def, for, while, import, or class, the assignment statement (a.k.a a = 42), or entire programs:

    >>> 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 - 它们是全局和代码看到的局部变量范围.这些默认为调用 execeval 的范围内的 globals()locals(),但任何字典可用于 globals 和任何 mapping 用于 locals(当然包括 dict).这些不仅可以用来限制/修改代码看到的变量,还经常用于捕获 executed 代码创建的变量:


    Both exec and eval accept 2 additional positional arguments - globals and locals - which are the global and local variable scopes that the code sees. These default to the globals() and locals() within the scope that called exec or eval, but any dictionary can be used for globals and any mapping for locals (including dict of course). These can be used not only to restrict/modify the variables that the code sees, but are often also used for capturing the variables that the executed code creates:

    >>> g = dict()
    >>> l = dict()
    >>> exec('global a; a, b = 123, 42', g, l)
    >>> g['a']
    123
    >>> l
    {'b': 42}
    

    (如果显示整个g的值,会长很多,因为execeval加了内置模块作为 __builtins__ 自动添加到全局变量(如果它丢失).

    (If you display the value of the entire g, it would be much longer, because exec and eval add the built-ins module as __builtins__ to the globals automatically if it is missing).

    在Python 2中,exec语句的官方语法实际上是exec code in globals, locals,如

    In Python 2, the official syntax for the exec statement is actually exec code in globals, locals, as in

    >>> exec 'global a; a, b = 123, 42' in g, l
    

    然而,替代语法 exec(code, globals, locals) 也一直被接受(见下文).

    However the alternate syntax exec(code, globals, locals) has always been accepted too (see below).

    compile(source, filename, mode, flags=0,dont_inherit=False, optimize=-1) 内置可用于加速使用 execeval 重复调用相同代码通过预先将源代码编译成 code 对象.mode 参数控制 compile 函数接受的代码片段类型及其生成的字节码类型.选择是 'eval''exec''single':

    The compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1) built-in can be used to speed up repeated invocations of the same code with exec or eval by compiling the source into a code object beforehand. The mode parameter controls the kind of code fragment the compile function accepts and the kind of bytecode it produces. The choices are 'eval', 'exec' and 'single':

    • 'eval' 模式需要一个表达式,并且会生成字节码,运行时会返回那个表达式的值:

    • 'eval' mode expects a single expression, and will produce bytecode that when run will return the value of that expression:

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

  • 'exec' accepts any kinds of python constructs from single expressions to whole modules of code, and executes them as if they were module top-level statements. The code object returns 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' 的有限形式,它接受包含 single 语句(或多个由 ; 分隔的语句)如果最后一个语句是表达式语句,则生成的字节码也 将该表达式的值的 repr 打印到标准输出(!).

  • 'single' is a limited form of 'exec' which accepts a source code containing a single statement (or multiple statements separated by ;) if the last statement is an expression statement, the resulting bytecode also prints the repr of the value of that expression to the standard output(!).

    if-elif-else 链,带有 elsetry<的循环/code> 及其 exceptelsefinally 块被视为单个语句.

    An if-elif-else chain, a loop with else, and try with its except, else and finally blocks is considered a single statement.

    包含 2 个顶级语句的源代码片段对于 'single' 来说是一个错误,但在 Python 2 中存在一个错误,有时允许多个顶级语句代码;只有第一个被编译;其余的被忽略:

    A source fragment containing 2 top-level statements is an error for the 'single', except in Python 2 there is a bug that sometimes allows multiple toplevel statements in the code; only the first is compiled; the rest are ignored:

    在 Python 2.7.8 中:

    In Python 2.7.8:

    >>> exec(compile('a = 5
    a = 6', '<string>', 'single'))
    >>> a
    5
    

    在 Python 3.4.2 中:

    And in Python 3.4.2:

    >>> exec(compile('a = 5
    a = 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结果代码.

    This is very useful for making interactive Python shells. However, the value of the expression is not returned, even if you eval the resulting code.

    因此 execeval 最大的区别实际上来自 compile 函数及其模式.

    Thus greatest distinction of exec and eval actually comes from the compile function and its modes.

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

    In addition to compiling source code to bytecode, compile supports compiling abstract syntax trees (parse trees of Python code) into code objects; and source code into abstract syntax trees (the ast.parse is written in Python and just calls compile(source, filename, mode, PyCF_ONLY_AST)); these are used for example for modifying source code on the fly, and also for dynamic code creation, as it is often easier to handle the code as a tree of nodes instead of lines of text in complex cases.

    虽然 eval 只允许您评估包含单个表达式的字符串,但您可以 eval 整个语句,甚至是已 的整个模块编译d成字节码;也就是说,在 Python 2 中,print 是一个语句,不能直接被 eval 引导:

    While eval only allows you to evaluate a string that contains a single expression, you can eval a whole statement, or even a whole module that has been compiled into bytecode; that is, with Python 2, print is a statement, and cannot be evalled directly:

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

    compile it with 'exec' mode into a code object and you can eval it; the eval function will return 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
    

    如果查看 evalexec CPython 3 中的源代码,这一点非常明显;他们都使用相同的参数调用 PyEval_EvalCode,唯一的区别是 exec 显式返回 None.

    If one looks into eval and exec source code in CPython 3, this is very evident; they both call PyEval_EvalCode with same arguments, the only difference being that exec explicitly returns None.

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

    One of the major differences in Python 2 is that exec is a statement and eval is a built-in function (both are built-in functions in Python 3). It is a well-known fact that the official syntax of exec in Python 2 is exec code [in globals[, locals]].

    不像大多数 Python 2-to-3 移植 指南 似乎 建议,CPython 2 中的 exec 语句也可以与 looks exactly 类似 exec Python 3 中的函数调用.原因是 Python 0.9.9 有 exec(code, globals, locals) 内置函数!并且该内置函数已替换为 exec 语句 在 Python 1.0 发布之前的某个地方.

    Unlike majority of the Python 2-to-3 porting guides seem to suggest, the exec statement in CPython 2 can be also used with syntax that looks exactly like the exec function invocation in Python 3. The reason is that Python 0.9.9 had the exec(code, globals, locals) built-in function! And that built-in function was replaced with exec statement somewhere before Python 1.0 release.

    由于不希望破坏与 Python 0.9.9 的向后兼容性,Guido van Rossum 在 1993 年添加了一个兼容性技巧:如果 code 是长度为 2 或 3 的元组,并且 globalslocals 没有传递到 exec 语句中,否则,code 将被解释为好像元组的第二个和第三个元素是 globalslocals 分别.即使在 Python 1.4 文档(最早的在线可用版本)中也没有提到兼容性黑客);因此许多移植指南和工具的作者并不知道,直到 于 2012 年 11 月再次记录 :

    Since it was desirable to not break backwards compatibility with Python 0.9.9, Guido van Rossum added a compatibility hack in 1993: if the code was a tuple of length 2 or 3, and globals and locals were not passed into the exec statement otherwise, the code would be interpreted as if the 2nd and 3rd element of the tuple were the globals and locals respectively. The compatibility hack was not mentioned even in Python 1.4 documentation (the earliest available version online); and thus was not known to many writers of the porting guides and tools, until it was documented again in November 2012:

    第一个表达式也可以是长度为 2 或 3 的元组.在这种情况下,必须省略可选部分.exec(expr, globals) 形式等价于 exec expr in globals,而 exec(expr, globals, locals) 形式等价于exec expr in globals, locals.exec 的元组形式提供了与 Python 3 的兼容性,其中 exec 是一个函数而不是语句.

    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 中,它被方便地称为前向兼容选项(为什么人们会因为完全有后向兼容选项而感到困惑),当它实际上已经存在了 20 年的向后兼容性.

    Yes, in CPython 2.7 that it is handily referred to as being a forward-compatibility option (why confuse people over that there is a backward compatibility option at all), when it actually had been there for backward-compatibility for two decades.

    因此 exec 在 Python 1 和 Python 2 中是一个语句,在 Python 3 和 Python 0.9.9 中是一个内置函数,

    Thus while exec is a statement in Python 1 and Python 2, and a built-in function in Python 3 and 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 的未记录行为表示赞赏).

    has had identical behaviour in possibly every widely released Python version ever; and works in Jython 2.5.2, PyPy 2.3.1 (Python 2.7.6) and IronPython 2.6.1 too (kudos to them following the undocumented behaviour of CPython closely).

    在 Pythons 1.0 - 2.7 中你不能做的就是将 exec 的返回值存储到一个变量中:

    What you cannot do in Pythons 1.0 - 2.7 with its compatibility hack, is to store the return value of exec into a variable:

    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 的引用:

    (which wouldn't be useful in Python 3 either, as exec always returns None), or pass a reference to exec:

    >>> call_later(exec, 'print(42)', delay=1000)
      File "<stdin>", line 1
        call_later(exec, 'print(42)', delay=1000)
                      ^
    SyntaxError: invalid syntax
    

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

    Which a pattern that someone might actually have used, though unlikely;

    或者在列表推导中使用它:

    Or use it in a list comprehension:

    >>> [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 循环代替!).

    which is abuse of list comprehensions (use a for loop instead!).

  • 相关文章