eval,exec和Python编译有什么区别?

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

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

简短的回答,或TL; DR

基本上, eval用于评估一个dynamic生成的Pythonexpression式,而exec用于仅为其副作用执行dynamic生成的Python代码。

evalexec有这两个区别:

  1. eval只接受一个expression式exec可以接受一个包含Python语句的代码块:loops, try: except:class和函数/方法定义等等。

    Python中的expression式就是variables赋值中的值:

     a_variable = (anything you can put within these parentheses is an expression) 
  2. eval 返回给定expression式的值 ,而exec忽略来自代码的返回值,并且总是返回None (在Python 2中,它是一个语句,不能用作expression式,所以它实际上不会返回任何东西)。

在版本1.0 – 2.7中, exec是一个声明,因为CPython需要为函数中的副作用使用exec生成一个不同types的代码对象。

在Python 3中, exec是一个函数; 它始终生成相同types的代码对象,无论它是在函数内还是在模块范围内调用。


基本上这样:

 >>> 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'模式下编译将任意数量的语句编译成一个总是返回None的字节码,而在'eval'模式下,它将单个expression式编译成返回该expression式值的字节码。

 >>> 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'模式下(如果传入string,则使用eval函数),如果源代码包含语句或其他超出单个expression式的语句,则compile会引发exception:

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

实际上,只有当一个string(包含Python 源代码 )传递给eval时, 才会使用“eval只接受单个expression式”语句。 然后使用compile(source, '<string>', 'eval')在内部编译为字节码。这是差异真正来自的地方。

如果一个code对象(包含Python 字节码 )被传递给execeval它们的行为是一样的 ,除了exec忽略返回值,仍然返回None 。 所以有可能使用eval来执行一些有语句的东西,如果你只是compilecompile成字节码,而不是把它作为string传递的话:

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

即使编译的代码包含语句,也可以毫无问题地工作。 它仍然返回None ,因为这是从compile返回的代码对象的返回值。

'eval'模式下(如果传入string,则使用eval函数),如果源代码包含语句或其他超出单个expression式的语句,则compile会引发exception:

 >>> 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中的语句 )用于执行dynamic创build的语句或程序:

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

eval函数对单个expression式执行相同的操作, 返回expression式的值:

 >>> a = 2 >>> my_calculation = '42 * a' >>> result = eval(my_calculation) >>> result 84 

execeval都接受程序/expression式作为包含源代码的strunicodebytes对象,或者作为包含Python字节码的code对象运行

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

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

eval类似的performance等同于:

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

由于所有expression式都可以在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个额外的位置参数 – globals和局部variables – 这是代码所看到的全局和局部variables范围。 这些默认为调用execeval的范围内的globals()locals() ,但任何字典都可以用于globalslocals任何mapping (当然包括dict )。 这些不仅可以用来限制/修改代码所看到的variables,还可以用于捕获exec代码创build的variables:

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

(如果显示整个g的值,则会更长,因为如果缺less, execeval添加内置模块__builtins__到全局variables)。

在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)可以用来加速通过execeval重复调用相同的代码,事先将源code编译成code对象。 mode参数控制compile函数接受的代码片段的types以及它产生的字节码的types。 select是'eval''exec''single'

  • 'eval'模式需要一个expression式,并且会产生字节码,当运行时会返回该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'接受从单个expression式到整个代码模块的任何types的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'的有限forms,如果最后一个语句是一个expression式语句,它接受一个包含单个语句的源代码(或由多个语句分隔的语句),结果字节码也会打印该expression式的值的repr到标准输出(!)

    一个ifelifelse链,一个带有else的循环,并tryexceptelsefinally块被认为是一个单独的语句。

    包含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生成的代码,expression式的值也不会返回

因此execeval最大区别实际上来自compile函数及其模式。


编译器除了将源代码编译成字节码之外,还支持将抽象语法树 (将Python代码parsing树)编译成code对象; 和源代码插入到抽象语法树中( ast.parse是用Python编写的,只是调用compile(source, filename, mode, PyCF_ONLY_AST) ); 这些例如用于dynamic地修改源代码以及用于dynamic代码创build,因为在复杂的情况下,将代码处理为树的节点而不是文本的行通常更容易。


虽然eval只允许你评估一个包含单个expression式的string,但你可以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对象,然后你可以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

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

Python 2的一个主要区别是exec是一个语句, eval是一个内置函数(都是Python 3中的内置函数)。 众所周知,Python 2中exec code [in globals[, locals]]的官方语法是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语句replace了。

由于不想破坏与Python 0.9.9的向后兼容性, Guido van Rossum在1993年增加了一个兼容性破解 :如果code是长度为2或3的元组,那么globalslocals globals不会被传递到exec语句中,否则, code将被解释为元组的第二和第三元素分别是globalslocals globals 。 即使在Python 1.4文档(在线最早的可用版本)中也没有提到兼容性攻击。 因此,许多移植指南和工具的作者都不知道,直到2012年11月再次被logging :

第一个expression式也可以是长度为2或3的元组。在这种情况下,必须省略可选部分。 表单exec(expr, globals)相当于exec expr in globals exec(expr, globals, locals)forms相当于exec expr in globals, localsexec的元组forms提供了与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的返回值存储到一个variables中:

 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 

尽pipe不太可能,但实际上有人可能使用哪种模式;

或者在列表理解中使用它:

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

这是滥用列表parsing(使用for循环代替!)。

  1. exec不是expression式:Python 2.x中的语句和Python 3.x中的函数。 它编译并立即评估包含在string中的语句或语句集合。 例:

     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是一个内置函数( 不是语句),它计算expression式并返回expression式产生的值。 例:

     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的低级版本。 它不会执行或计算您的语句或expression式,但会返回一个可以执行的代码对象。 模式如下:

    1. compile(string, '', 'eval')返回你已经完成eval(string)的代码对象。 请注意,您不能在此模式下使用语句; 只有一个(单个)expression式是有效的。
    2. compile(string, '', 'exec')返回你已经执行的代码对象exec(string) 。 你可以在这里使用任何数量的语句。
    3. compile(string, '', 'single')就像exec模式,但是除了第一个语句之外,它会忽略所有的东西。 请注意, if / else语句及其结果被视为单个语句。

exec是为了声明而不返回任何东西。 eval用于expression式并返回expression式的值。

expression意思是“某事”,而expression意思是“做某事”。