eval,exec和compile有什么区别?

不羁的心 提交于 2020-02-26 09:36:51

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

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


#1楼

exec用于语句,不返回任何内容。 eval用于表达式,并返回表达式的值。

表达式表示“某事”,而语句表示“做某事”。


#2楼

  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的较低版本。 它不会执行或评估您的语句或表达式,但会返回可以执行此操作的代码对象。 模式如下:

    1. compile(string, '', 'eval')返回如果完成eval(string)将会执行的代码对象。 请注意,您不能在这种模式下使用语句。 仅(单个)表达式有效。
    2. compile(string, '', 'exec')返回如果执行完exec(string)后将要执行的代码对象。 您可以在此处使用任意数量的语句。
    3. compile(string, '', 'single')类似于exec模式,但是它将忽略除第一条语句以外的所有内容。 请注意,带有结果的if / else语句被视为单个语句。

#3楼

简短答案,即TL; DR

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

evalexec有以下两个区别:

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

    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

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)', '<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时,语句“ eval只接受一个表达式”才适用。 然后使用compile(source, '<string>', 'eval')在内部将其编译为字节码。这才是真正的区别。

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

>>> 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字节码的code对象运行

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

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

在2个程序中,只有exec接受包含语句的源代码,例如defforwhileimportclass ,赋值语句(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 -这是全局和局部变量的作用域,该代码看到。 它们默认为execeval范围内的globals()locals() ,但是任何字典都可以用于globals以及任何locals mapping (当然包括dict )。 这些不仅可以用于限制/修改代码中看到的变量,而且还经常用于捕获exec代码创建的变量:

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

compile

内置的compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)可以通过预先将源code编译为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'接受从单一表达式到整个代码模块的各种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使用其exceptelsefinally块被视为单个语句。

    包含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结果代码,也不会返回表达式的值。

因此, execeval最大区别实际上来自于compile功能及其模式。


除了将源代码编译为字节码外, compile支持将抽象语法树 (Python代码的解析树)编译为code对象。 并将源代码转换成抽象语法树( ast.parse用Python编写,仅调用compile(source, filename, mode, PyCF_ONLY_AST) ); 它们被用于例如动态修改源代码,以及用于动态代码创建,因为在复杂情况下,将代码作为节点树而不是文本行来处理通常会更容易。


虽然eval仅允许您评估包含单个表达式的字符串,但是您可以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

'exec'模式将其compilecode对象,然后可以进行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

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

Python 2和Python 3之间exec语法差异

Python 2的主要区别之一是exec是一个语句, eval是一个内置函数(这两个都是Python 3中的内置函数)。 这是一个众所周知的事实,正式语法exec在Python 2 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将被解释为好像元组的第2个元素和第3个元素分别是globalslocals 。 即使在Python 1.4文档(在线最早可用的版本)中也没有提到兼容性hack; 因此对于移植指南和工具的许多作者并不了解,直到2012年11月再次对其进行了记录

第一个表达式也可以是长度为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

(这在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循环!)。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!