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

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

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

265734 次浏览
  1. exec不是表达式:Python 2中的语句。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')

exec是for语句,不返回任何东西。 Eval用于表达式,返回表达式的值

Expression表示“某事”,statement表示“做某事”。

简而言之,TL, DR

基本上,eval用于# eyz3ute单个动态生成的Python表达式,而exec用于执行ute动态生成的Python代码仅用于其副作用。

evalexec有两个区别:

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

    Python中的表达式是你可以在变量赋值中作为值的任何东西:

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

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.

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


Thus basically:

>>> 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'模式下(如果传入字符串,则使用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来执行一些有语句的东西,如果你只是compiled它在字节码之前,而不是作为字符串传递它:

>>> 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函数(原来是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对象运行,或者作为包含Python字节码的# EYZ5对象对象运行。

如果一个包含源代码的str/unicode/bytes被传递给exec,它的行为相当于:

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

eval的行为类似于:

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

由于在Python中所有表达式都可以作为语句使用(在Python的抽象语法;反之则不正确),如果你不需要返回值,可以使用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接受包含语句的源代码,如defforwhileimport,或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——这是代码所看到的全局变量和局部变量作用域。在调用execeval的范围内,这些默认为globals()locals(),但任何字典都可以用于globals,任何mapping用于locals(当然包括eval1)。它们不仅可以用来限制/修改代码所看到的变量,而且通常也用于捕获# eyz0ured代码创建的变量:

>>> 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)也一直被接受(见下文)。

# EYZ0

内置的compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)可以通过预先将源代码编译为code对象来加速重复调用execeval的相同代码。mode参数控制了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' 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' 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(!).

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

    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:

    In 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一个完整的语句,甚至是一个完整的模块,已经compiled到字节码;也就是说,在python2中,print是一个语句,不能直接使用evalled:

>>> 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对象,你可以# EYZ3它;函数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

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

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

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

与大多数Python 2-to-3 移植 指南 似乎 建议不同,CPython 2中的exec语句也可以与看起来 完全语法一起使用,就像Python 3中的exec函数调用一样。原因是Python 0.9.9有exec(code, globals, locals)内置函数!这个内置函数被exec语句exec0所取代。

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

第一个表达式也可以是长度为2或3的元组。在这种情况下,可选部分必须省略。形式exec(expr, globals)相当于exec expr in globals,而形式exec(expr, globals, locals)相当于exec expr in globals, localsexec的元组形式提供了与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

(这在python3中也没有用,因为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循环代替!)。