一尘不染

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

python

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

有人可以解释之间的区别evalexec怎样的不同模式,compile()适应吗?


阅读 399

收藏
2020-02-12

共1个答案

一尘不染

简短答案,即TL; DR
基本上,eval用于EVAL审视你们单个动态生成的Python表达式,并exec用于EXEC动态生成的Python代码仅针对其副作用尤特。

evalexec具有以下两个区别:

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

compile(‘for i in range(3): print(i)’, ‘‘, ‘eval’)
Traceback (most recent call last):
File ““, line 1, in
File ““, line 1
for i in range(3): print(i)
^
SyntaxError: invalid syntax


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

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

eval(compile(‘if 1: print(“Hello”)’, ‘‘, ‘exec’))
Hello


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

在这种`'eval'`模式下(eval如果传递了一个字符串,则在函数中),compile如果源代码包含语句或除单个表达式之外的任何其他内容,则会引发异常:

compile(‘for i in range(3): print(i)’, ‘‘. ‘eval’)
Traceback (most recent call last):
File ““, line 1, in
File ““, 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, ‘‘, ‘exec’))


并且eval类似地相当于:

eval(compile(source, ‘‘, ‘eval’))


由于所有表达式都可以用作Python中的语句(Expr在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


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 ““, line 1, in
File ““, 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


(如果你显示整个的价值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'`模式需要一个表达式,并将生成字节码,运行时将返回该表达式的值:

dis.dis(compile(‘a + b’, ‘‘, ‘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’, ‘‘, ‘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到标准output(!)上。

一个if`- elif- else`链,有一个循环`else`,并try用它`except,else`并且`finally`块被视为一个单独的语句。

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

在Python 2.7.8中:

exec(compile(‘a = 5\na = 6’, ‘‘, ‘single’))
a
5


在Python 3.4.2中:

exec(compile(‘a = 5\na = 6’, ‘‘, ‘single’))
Traceback (most recent call last):
File ““, line 1, in
File ““, 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 ““, line 1, in
File ““, 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显式返回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将被解释就像元组的第二个元素和第三个元素分别是`globals`和一样`locals`。即使在Python 1.4文档(在线最早可用的版本)中也没有提到兼容性hack ;因此对于移植指南和工具的许多作者并不了解,直到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的未记录的行为表示敬意)。

在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 ““, line 1
a = exec(‘print(42)’)
^
SyntaxError: invalid syntax


(这在Python 3中也没有用,因为它`exec`总是返回`None`),或将引用传递给`exec`:

call_later(exec, ‘print(42)’, delay=1000)
File ““, line 1
call_later(exec, ‘print(42)’, delay=1000)
^
SyntaxError: invalid syntax


某人可能实际使用过的一种模式,尽管可能性不大;

或在列表理解中使用它:

[exec(i) for i in [‘print(42)’, ‘print(foo)’]
File ““, line 1
[exec(i) for i in [‘print(42)’, ‘print(foo)’]
^
SyntaxError: invalid syntax
```

这是对列表理解的滥用(请for改为使用循环!)。

2020-02-12