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

575 投票
3 回答
290974 浏览
提问于 2025-04-15 19:01

我最近在研究如何动态执行Python代码,发现了eval()compile()这两个函数,还有exec这个语句。

有人能帮我解释一下evalexec之间的区别吗?还有compile()的不同模式是怎么回事?

3 个回答

54

exec是用来执行语句的,它不会返回任何结果。

eval是用来计算表达式的,它会返回表达式的值。

这里“表达式”指的是“某个东西”,而“语句”则指的是“做某件事”。

216
  1. exec 在 Python 2.x 中是一个语句,而在 Python 3.x 中是一个函数。它的作用是把字符串中的语句编译并立即执行。举个例子:

     exec('print(5)')           # prints 5.
     # exec 'print 5'     if you use Python 2.x, nor the exec neither the print is a function there
     exec('print(5)\nprint(6)')  # prints 5{newline}6.
     exec('if True: print(6)')  # prints 6.
     exec('5')                 # does nothing and returns nothing.
    
  2. eval 是一个内置函数(不是语句),它的作用是计算一个表达式,并返回这个表达式的结果。举个例子:

     x = eval('5')              # x <- 5
     x = eval('%d + 6' % x)     # x <- 11
     x = eval('abs(%d)' % -100) # x <- 100
     x = eval('x = 5')          # INVALID; assignment is not an expression.
     x = eval('if 1: x = 4')    # INVALID; if is a statement, not an expression.
    
  3. compileexeceval 的一个更底层的版本。它不会直接执行你的语句或表达式,而是返回一个可以执行这些语句或表达式的代码对象。它有几种模式:

  4. compile(string, '', 'eval') 返回一个代码对象,这个对象如果你执行 eval(string) 的话会被执行。注意,这种模式下你不能使用语句,只能用一个(单个)表达式。

  5. compile(string, '', 'exec') 返回一个代码对象,这个对象如果你执行 exec(string) 的话会被执行。在这里你可以使用任意数量的语句。

  6. compile(string, '', 'single') 类似于 exec 模式,但它要求正好有一个表达式/语句,比如 compile('a=1 if 1 else 3', 'myf', mode='single')

735

简短回答,或者说TL;DR

简单来说,eval 是用来计算一个动态生成的Python表达式,而 exec 是用来执行动态生成的Python代码,主要是为了它的副作用。

evalexec 有这两个区别:

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

    在Python中,表达式就是你可以用来给变量赋值的任何东西:

    a_variable = (anything you can put within these parentheses is an expression)
    
  2. 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

'exec' 模式下,compile 会将任意数量的语句编译成一个字节码,这个字节码隐式地总是返回 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' 模式下(如果传入的是字符串),如果源代码包含语句或其他超出单一表达式的内容,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 源代码)时。然后它会被内部编译成字节码,使用 compile(source, '<string>', 'eval')。这就是区别的真正来源。

如果将一个 code 对象(包含Python 字节码)传递给 execeval它们的行为是相同的,唯一的区别是 exec 会忽略返回值,始终返回 None。所以可以使用 eval 来执行包含语句的内容,只要你先将其 compile 成字节码,而不是作为字符串传递:

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

这样做没有问题,即使编译的代码包含语句。它仍然返回 None,因为这是从 compile 返回的代码对象的返回值。

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

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

eval 函数则用于处理一个单一表达式,并且返回该表达式的值:

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

execeval 都可以接受要运行的程序/表达式,格式可以是 strunicodebytes 对象,包含源代码,或者是一个 code 对象,它包含Python字节码。

如果传递给 exec 的是一个 str/unicode/bytes,它的行为相当于:

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

eval 的行为类似于:

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

由于所有表达式都可以在Python中作为语句使用(这些在Python的抽象语法中称为 Expr 节点;反之则不成立),如果你不需要返回值,你总是可以使用 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
>>> 

在这两者中,只有 exec 接受包含语句的源代码,比如 defforwhileimportclass,赋值语句(也就是 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 还接受两个额外的位置参数 - globalslocals - 这两个参数是代码所能看到的全局和局部变量范围。默认情况下,它们是调用 execeval 的范围内的 globals()locals(),但可以使用任何字典作为 globals,任何 mapping 作为 locals(当然包括 dict)。这些不仅可以用来限制/修改代码所能看到的变量,通常也用于捕获 exec 执行的代码所创建的变量:

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

(如果你显示整个 g 的值,它会更长,因为如果缺少 __builtins__execeval 会自动将内置模块添加为全局变量)。

在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) 内置函数可以通过提前将源代码编译成 code 对象来加速对相同代码的重复调用,使用 execevalmode 参数控制 compile 函数接受的代码片段类型以及它生成的字节码类型。可选的值有 'eval''exec''single'

  • 'eval' 模式期望一个单一表达式,并会生成字节码,当运行时会返回该表达式的值:

    >>> 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 的循环,以及带有 exceptelsefinally 块的 try 被视为单一语句。

    包含两个顶层语句的源代码片段在 '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 还支持将抽象语法树(Python代码的解析树)编译成 code 对象;以及将源代码编译成抽象语法树(ast.parse 是用Python编写的,只是调用 compile(source, filename, mode, PyCF_ONLY_AST));这些通常用于动态修改源代码,以及动态代码创建,因为在复杂情况下,处理代码作为节点树而不是文本行更容易。


虽然 eval 只允许你计算一个包含单一表达式的字符串,但你可以将整个语句,甚至整个模块编译成字节码后再 eval;也就是说,在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

将其用 'exec' 模式编译成 code 对象后,你就可以evaleval 函数将返回 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

Python 2和Python 3中exec的语法差异

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

与大多数Python 2到3的移植 指南 似乎 建议,在CPython 2中,exec 语句也可以使用看起来完全像Python 3中 exec 函数调用的语法。原因是Python 0.9.9中有 exec(code, globals, locals) 这个内置函数!而这个内置函数在Python 1.0发布之前被替换为 exec 语句。

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

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

是的,在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的未记录行为)。

在Python 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 循环吧!)。

撰写回答