eval,exec和Python编译有什么区别?
我一直在研究Python代码的dynamic评估,并且遇到了eval()
和compile()
函数以及exec
语句。
有人可以解释eval
和exec
之间的区别,以及compile()
的不同模式如何适用?
简短的回答,或TL; DR
基本上, eval
用于评估一个dynamic生成的Pythonexpression式,而exec
用于仅为其副作用执行dynamic生成的Python代码。
eval
和exec
有这两个区别:
-
eval
只接受一个expression式 ,exec
可以接受一个包含Python语句的代码块:loops,try: except:
,class
和函数/方法定义等等。Python中的expression式就是variables赋值中的值:
a_variable = (anything you can put within these parentheses is an expression)
-
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 字节码 )被传递给exec
或eval
, 它们的行为是一样的 ,除了exec
忽略返回值,仍然返回None
。 所以有可能使用eval
来执行一些有语句的东西,如果你只是compile
它compile
成字节码,而不是把它作为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
更长的答案,又名血淋淋的细节
exec
和eval
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
exec
和eval
都接受程序/expression式作为包含源代码的str
, unicode
或bytes
对象,或者作为包含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
接受包含语句(如def
, for
, while
, import
或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
exec
和eval
接受2个额外的位置参数 – globals
和局部variables – 这是代码所看到的全局和局部variables范围。 这些默认为调用exec
或eval
的范围内的globals()
和locals()
,但任何字典都可以用于globals
和locals
任何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, exec
和eval
添加内置模块__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)
可以用来加速通过exec
或eval
重复调用相同的代码,事先将源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
到标准输出(!) 。一个
if
–elif
–else
链,一个带有else
的循环,并try
其except
,else
和finally
块被认为是一个单独的语句。包含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式的值也不会返回 。
因此exec
和eval
最大区别实际上来自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'
模式compile
成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中查看eval
和exec
源代码,这是非常明显的; 它们都使用相同的参数调用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的元组,那么globals
和locals
globals
不会被传递到exec
语句中,否则, code
将被解释为元组的第二和第三元素分别是globals
和locals
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, locals
。exec
的元组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
循环代替!)。
-
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.
-
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.
-
compile
是exec
和eval
的低级版本。 它不会执行或计算您的语句或expression式,但会返回一个可以执行的代码对象。 模式如下:-
compile(string, '', 'eval')
返回你已经完成eval(string)
的代码对象。 请注意,您不能在此模式下使用语句; 只有一个(单个)expression式是有效的。 -
compile(string, '', 'exec')
返回你已经执行的代码对象exec(string)
。 你可以在这里使用任何数量的语句。 -
compile(string, '', 'single')
就像exec
模式,但是除了第一个语句之外,它会忽略所有的东西。 请注意,if
/else
语句及其结果被视为单个语句。
-
exec是为了声明而不返回任何东西。 eval用于expression式并返回expression式的值。
expression意思是“某事”,而expression意思是“做某事”。