Python中的eval,exec和compile有什么区别?


Python中的eval,exec和compile有什么区别?


简短的答案

基本上,eval用于EVAL审视你们单个动态生成的Python表达式,并exec用于exec的 UTE动态生成的Python代码只为它的副作用。

eval并exec有这两个区别:

  1. eval只接受一个表达式,exec可以采用具有Python语句的代码块:循环try: except:,class和函数/方法def引用等。

Python中的表达式是变量赋值中的值:

a_variable = (anything you can put within these parentheses is an expression)
  1. 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'模式编译任何数目的语句编译成字节码隐含总是返回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如果源代码包含语句或单个表达式之外的任何其他内容,则引发异常:

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

实际上,只有在传递一个字符串(包含Python 源代码)时,语句“eval只接受一个表达式”才适用。然后它在内部编译为字节码使用这是差异真正来自。evalcompile(source, '<string>', 'eval')

如果一个code对象(包含Python 字节码)被传递给exec或者eval,它们的行为相同,除了exec忽略返回值的事实,仍然None总是返回。因此eval,如果您只是将compile其转换为字节码而不是将其作为字符串传递,则可以使用执行具有语句的内容:

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

详细的答案

exec 和 eval

该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

exec和eval均接受该程序/表达来运行或者作为str,unicode或bytes包含对象的源代码,或作为code对象包含的Python字节码。

如果str/ unicode/ bytes包含源代码传递给exec,它等效行为与:

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

并且eval类似地表现为:

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

由于所有表达式都可以用作Python中的语句(这些语句在Python 抽象语法中称为Expr节点;反之亦然),如果不需要返回值,则可以随时使用。也就是说,您可以使用或者,差异是返回返回的值,并丢弃它:execeval('my_func(42)')exec('my_func(42)')evalmy_funcexec

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

双方exec并eval接受2个额外的位置参数- globals和locals-这是该代码看到全局和局部变量的作用域。这些默认为globals()与locals()内调用的范围exec或eval,但任何字典可用于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,这将是更长的时间,因为exec和eval内置插件模块添加为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)内置的可用于加快与相同的码的重复调用exec或eval通过编译源到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,else并且finally块被视为一个单独的语句。

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

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

因此,最大的区别exec和eval实际来自compile功能及其模式。

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

虽然eval只允许您评估包含单个表达式的字符串,但您可以eval使用整个语句,甚至可以将整个模块compile转换为字节码; 也就是说,使用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

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

execPython 2和Python 3之间的语法差异

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

不像大多数的Python 2到3的移植 指南 似乎 表明,对exec在CPython的2语句也可以使用语法看起来 完全相同像exec在Python 3函数调用的原因就是,Python 0.9.9有exec(code, globals, locals)内置在功能!在Python 1.0发布之前,内置函数已被exec语句替换。

由于这是可取的不破与Python 0.9.9向后兼容性,吉多·范罗苏姆在1993年增加了兼容性劈:如果code是长度为2或3的元组,并globals与locals未传递到exec声明,否则,code将被解释好像元组的第2和第3个元素分别是globals和locals。即使在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的兼容性,其中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的未记录行为密切关注他们)。

在Pythons 1.0 - 2.7及其兼容性hack中你不能做的是将返回值存储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循环代替!)。