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

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


exec is not an expression: a statement in Python 2.x, and a function in Python 3.x. It compiles and immediately evaluates a statement or set of statement contained in a string. Example: 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. eval is a built-in function (not a statement), which evaluates an expression and returns the value that expression produces. Example: 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. compile is a lower level version of exec and eval. It does not execute or evaluate your statements or expressions, but returns a code object that can do it. The modes are as follows: compile(string, '', 'eval') returns the code object that would have been executed had you done eval(string). Note that you cannot use statements in this mode; only a (single) expression is valid. compile(string, '', 'exec') returns the code object that would have been executed had you done exec(string). You can use any number of statements here. compile(string, '', 'single') is like the exec mode but expects exactly one expression/statement, eg compile('a=1 if 1 else 3', 'myf', mode='single')

Exec用于语句,不返回任何东西。 Eval用于表达式,返回表达式的值。

Expression表示“某事”,statement表示“做某事”。

简而言之,TL, DR

基本上,eval用于对单个动态生成的Python表达式求值,而exec用于执行动态生成的Python代码仅用于其副作用。

Eval和exec有两个区别:

eval accepts only a single expression, exec can take a code block that has Python statements: loops, try: except:, class and function/method definitions and so on. An expression in Python is whatever you can have as the value in a variable assignment: a_variable = (anything you can put within these parentheses is an expression) eval returns the value of the given expression, whereas exec ignores the return value from its code, and always returns None (in Python 2 it is a statement and cannot be used as an expression, so it really does not return anything).

在1.0 - 2.7版本中,exec是一个语句,因为CPython需要为函数生成一种不同类型的代码对象,这些函数使用exec来处理函数内部的副作用。

在python3中,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('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')将其编译为字节码。这就是真正的区别所在。

如果一个代码对象(包含Python字节码)被传递给exec或eval,它们的行为是相同的,除了exec忽略返回值,仍然总是返回None。因此,使用eval来执行一些有语句的东西是可能的,如果你只是在将它编译成字节码而不是作为字符串传递它:

>>> eval(compile('if 1: print("Hello")', '<string>', 'exec'))
Hello
>>>

即使编译后的代码包含语句,也不会出现任何问题。它仍然返回None,因为这是compile返回的code对象的返回值。

在'eval'模式下(如果传入了字符串,则使用eval函数),如果源代码包含语句或任何超出单个表达式的内容,编译器将引发异常:

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

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对象运行,或者作为包含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接受包含语句的源代码,如def, for, while, import,或class,赋值语句(a.k.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——这是代码所看到的全局变量和局部变量作用域。在调用exec或eval的作用域内,这些默认为globals()和locals(),但任何字典都可以用于全局变量,任何映射都可以用于局部变量(当然包括dict)。这些变量不仅可以用来限制/修改代码看到的变量,而且通常也用于捕获执行代码创建的变量:

>>> g = dict()
>>> l = dict()
>>> exec('global a; a, b = 123, 42', g, l)
>>> g['a']
123
>>> l
{'b': 42}

(如果你显示整个g的值,它会长得多,因为exec和eval会自动将内置模块__builtins__添加到全局变量中,如果它缺失的话)。

在python2中,exec语句的正式语法实际上是在全局变量、局部变量中的exec代码,例如

>>> exec 'global a; a, b = 123, 42' in g, l

然而,替代语法exec(code, globals, locals)也一直被接受(见下文)。

编译

内置的compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)可以通过预先将源代码编译为code对象来加速exec或eval对相同代码的重复调用。mode参数控制编译函数接受的代码片段类型和生成的字节码类型。选项是'eval', 'exec'和'single':

'eval' mode expects a single expression, and will produce bytecode that when run will return the value of that expression: >>> 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' accepts any kinds of python constructs from single expressions to whole modules of code, and executes them as if they were module top-level statements. The code object returns 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' is a limited form of 'exec' which accepts a source code containing a single statement (or multiple statements separated by ;) if the last statement is an expression statement, the resulting bytecode also prints the repr of the value of that expression to the standard output(!). An if-elif-else chain, a loop with else, and try with its except, else and finally blocks is considered a single statement. A source fragment containing 2 top-level statements is an error for the 'single', except in Python 2 there is a bug that sometimes allows multiple toplevel statements in the code; only the first is compiled; the rest are ignored: In Python 2.7.8: >>> exec(compile('a = 5\na = 6', '<string>', 'single')) >>> a 5 And in 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 This is very useful for making interactive Python shells. However, the value of the expression is not returned, even if you eval the resulting code.

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


除了将源代码编译为字节码外,compile还支持将抽象语法树(Python代码的解析树)编译为代码对象;将源代码转换为抽象语法树(ast.parse是用Python编写的,只调用compile(源,文件名,模式,PyCF_ONLY_AST));例如,它们用于动态修改源代码,也用于动态代码创建,因为在复杂的情况下,将代码作为节点树而不是文本行处理通常更容易。


虽然eval只允许你计算包含单个表达式的字符串,但你可以计算整个语句,甚至是编译成字节码的整个模块;也就是说,在Python 2中,print是一个语句,不能直接求值:

>>> 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'模式将它编译成一个代码对象,你可以对它进行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。

Python 2和Python 3中exec的语法差异

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

与大多数Python 2-to-3移植指南所建议的不同,CPython 2中的exec语句也可以使用与Python 3中的exec函数调用完全相同的语法。原因是Python 0.9.9有内置的exec(code, globals, locals)函数!在Python 1.0发布之前,这个内置函数已经被exec语句所取代。

Since it was desirable to not break backwards compatibility with Python 0.9.9, Guido van Rossum added a compatibility hack in 1993: if the code was a tuple of length 2 or 3, and globals and locals were not passed into the exec statement otherwise, the code would be interpreted as if the 2nd and 3rd element of the tuple were the globals and locals respectively. The compatibility hack was not mentioned even in Python 1.4 documentation (the earliest available version online); and thus was not known to many writers of the porting guides and tools, until it was documented again in November 2012:

第一个表达式也可以是长度为2或3的元组。在这种情况下,可选部分必须省略。form exec(expr, globals)相当于globals中的exec expr,而form exec(expr, globals, locals)相当于globals, locals中的exec expr。exec的元组形式提供了与Python 3的兼容性,在Python 3中,exec是一个函数而不是语句。

是的,在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, 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循环代替!)