小编典典

eval、exec 和 compile 有什么区别?

all

我一直在研究 Python 代码的动态评估,遇到了eval()andcompile()函数和exec语句。

有人可以解释 和 之间的区别evalexec以及不同的适应模式如何compile()吗?


阅读 131

收藏
2022-03-11

共1个答案

小编典典

简短的回答,或 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)
  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

compilein模式将'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)', '<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 字节码 )被传递给execor 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

更长的答案,也就是血淋淋的细节

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

exec并且eval都接受程序/表达式作为包含源代码str对象unicodebytes作为包含 Python
字节码的对象运行。 code

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

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

eval类似的行为等同于:

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

由于 Python 中的所有表达式都可以用作语句(在
Python抽象语法Expr中称为节点;反之则不然),如果不需要返回值,则始终可以使用。也就是说,你可以使用or ,不同之处在于返回
的返回值,并丢弃它: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, or class, 赋值语句
(aka 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-
这是代码看到的全局和局部变量范围。这些默认为globals()andlocals()在调用execor的范围内eval,但是任何字典都可以用于globals和任何mappingfor
localsdict当然包括)。这些不仅可以用于限制/修改代码看到的变量,还可以用于捕获execute 代码创建的变量:

>>> 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将源编译到对象中。codemode参数控制函数接受的代码片段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

'single'包含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整个语句,甚至是已被compiled 转换为字节码的整个模块;也就是说,在
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
evalexec源代码,这一点非常明显;它们都PyEval_EvalCode使用相同的参数调用,唯一的区别是exec显式返回None.

execPython 2 和 Python 3的语法差异

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

与大多数 Python 2 到 3移植
指南
似乎
建议的不同,CPython
2 中的exec语句也可以使用与Python 3 中的函数调用 完全相同* 的语法。原因是 Python 0.9.9 具有 内置
的-在功能!在 Python 1.0 release
之前的某个地方
,该内置函数被替换为语句。
*exec``exec(code, globals, locals)``exec

由于希望不破坏与 Python 0.9.9 的向后兼容性,Guido van Rossum 在 1993
年添加了一个兼容性技巧
:如果code是长度为
2 或 3
的元组,globals并且locals没有传递到exec语句中,code则将被解释好像元组的第二个和第三个元素分别是globalsand
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 中,它被方便地称为向前兼容选项(为什么人们会因为有向后兼容选项而感到困惑),而实际上它已经存在了 20 年的向后兼容性

因此 whileexec是 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循环代替!)。

2022-03-11