eval、exec和compile有什么区别?

2024-04-18 07:15:34 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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


Tags: 函数代码eval模式动态语句execcompile
3条回答

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

表达意味着“某事”,而陈述意味着“做某事”。

  1. exec不是表达式:Python2.x中的语句和Python3.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是一个内置函数(not语句),它计算表达式并返回表达式生成的值。示例:

    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语句及其结果被视为单个语句。

简短的回答,或TL;DR

基本上,^{}用于评估一个动态生成的Python表达式,而^{}用于执行动态生成的Python代码,只是因为其副作用。

evalexec有以下两个区别:

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

    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

'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时应用。然后在内部使用^{}编译成字节码,这就是区别真正的来源。

如果code对象(包含Python字节码)被传递给execeval它们的行为完全相同,除了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

^{}函数(以前是a statement in Python 2)用于执行动态创建的语句或程序:

>>> program = '''
for i in range(3):
    print("Python is cool")
'''
>>> exec(program)
Python is cool
Python is cool
Python is cool
>>> 

^{}函数对single expression执行相同的操作,返回表达式的值:

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

其中,只有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都接受另外两个位置参数-globalslocals-这是代码看到的全局和局部变量作用域。这些默认值为globals()locals()在调用execeval的作用域内,但是任何字典都可以用于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的值,则会更长,因为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

内置的^{}可以通过预先将源代码编译成code对象来加速对同一代码的重复调用。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的循环以及带exceptelsefinallytry块被认为是一个语句。

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

    在Python2.7.8中:

    >>> exec(compile('a = 5\na = 6', '<string>', 'single'))
    >>> a
    5
    

    在Python3.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还支持将abstract syntax trees(Python代码的解析树)编译为code对象;将源代码编译为抽象语法树(用Python编写的ast.parse只调用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'mode进入一个code对象,您可以将其;函数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中的^{}^{}源代码,这一点非常明显;它们都使用相同的参数调用PyEval_EvalCode,唯一的区别是^{} explicitly returns ^{}

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

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

与大多数Python 2-to-3portingguidesseemto suggest不同,cpython2中的exec语句也可以使用与Python 3中的exec函数调用完全类似的语法。原因是Python 0.9.9具有内置的exec(code, globals, locals)函数!这个内置函数被exec语句somewhere before Python 1.0 release替换。

由于不需要破坏与Python 0.9.9、Guido van Rossum added a compatibility hack in 1993的向后兼容性:如果code是长度为2或3的元组,并且globalslocals没有传入exec语句,那么code将被解释为元组的第2和第3个元素分别是globalslocals。即使在Python 1.4 documentation (the earliest available version online)中也没有提到兼容性黑客;因此,许多移植指南和工具的作者都不知道它,直到它再次被documented使用为止:

The first expression may also be a tuple of length 2 or 3. In this case, the optional parts must be omitted. The form exec(expr, globals) is equivalent to exec expr in globals, while the form exec(expr, globals, locals) is equivalent to exec expr in globals, locals. The tuple form of exec provides compatibility with Python 3, where exec is a function rather than a statement.

是的,在CPython 2.7中,它被简单地称为前向兼容选项(为什么人们会对后向兼容选项感到困惑), 当它实际上已经存在了20年的向后兼容性时。

因此,虽然exec是Python 1和Python 2中的语句,是Python 3和Python 0.9.9中的内置函数

>>> exec("print(a)", globals(), {'a': 42})
42

可能在所有广泛发布的Python版本中都有相同的行为;在Jython 2.5.2、pypy2.3.1(python2.7.6)和ironpython2.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 "<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循环!)。

相关问题 更多 >