在Python中,val、exec和 compile 有什么区别?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (3)
  • 关注 (0)
  • 查看 (870)

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

谁能解释一下evalexec的不同,以及compile()的不同模式代表着什么?

提问于
用户回答回答于

简短的版本

基本上 eval 上用来评估一个动态生成的 Python 表达式;exec 额外的用于执行动态生成的 python 代码。

eval 和 exec 有以下两个差异

eval 只接受一个表达式,exec 可以接受一个包含了 python 语句的代码块: loops, try: except:, class 以及定义的函数和方法

Python 中的表达式是任何可以作为变量赋值中的值的表达式:

a_variable = (任何你可以放在这个括号内的都是一个表达式)

evale 返回表达式的值,而 exec 忽略返回值的代码,并且使用返回 None(在 Python 2 中这是一个声明,而不是一个表达式,所以没有任何返回)

在 1.0 - 2.7 版本中,exec 是一个声明,它总是为函数产生相同类型的代码对象,因为需要使用 exec 在函数中产生一个不同的编码对象。

在 python3 中,exec 上一个函数,它总是产生相同类型的代码对象,不管是在函数内部还是模块范围内调用的。

因此,基本上是这样的

>>> a = 5
>>> eval('37 + a')   # 这是一个表达式
42
>>> exec('37 + a')   # 这时一个表达式声明,返回值被忽略了(因为返回的是 None )
>>> exec('a = 47')   # 修改一个全局变量
>>> a
47
>>> eval('a = 47')  # 你无法评估一个表达式
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1
    a = 47
          ^
SyntaxError: invalid syntax

在'exec'模式下编译,将任意数量的语句编译成 总是返回None的字节码,而在'eval'模式下,它将单个表达式编译成返回该表达式值的字节码。

>>> eval(compile('42', '<string>', 'exec'))  # 代码返回 None
>>> eval(compile('42', '<string>', 'eval'))  # 代码返回42
42
>>> exec(compile('42', '<string>', 'eval'))  #  代码返回 42,但是被 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代码的字符串传入进来是,他才会内部进行编译,生成字节码,compile(source, '<string>', 'eval'),这是差异真正来自的地方。

如果一个包含了 python 字节码的编码对象被传递给了 exec 和 eval ,他们的行为是一样的,区别仅仅在于 exce 忽略返回值,仍然返回 None。

所以如果你只想编译城字节码而不是作为字符串传递的化,可以使用 eval 来执行包含语句的东西。

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

Hello

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

在 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

更长的版本

exec and 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 都接受程序/表达式作为包含源代码的strunicodebytes对象,或者作为包含 Python字节码的代码对象运行。

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

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

和eval类似的表现等同于:

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

由于所有表达式都可以在Python中用作语句(这些语句在Python抽象语法中被称为Expr节点;反之亦然),如果不需要返回值,则可以始终使用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

>>>

在 Python 2 中,只有exec接受包含语句(如def,for,while,import或class),赋值语句(a.k.a 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个额外的位置参数 - 全局变量和局部变量 - 这是代码所看到的全局和局部变量范围。 这些默认为调用exec或eval的范围内的globals()locals(),但任何字典都可以用于全局变量和任何本地映射(当然包括字典)。 这些不仅可以用来限制/修改代码所看到的变量,还可以用于捕获执行代码创建的变量:

>>> g = dict()

>>> l = dict()

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

>>> g['a']

123

>>> l

{'b': 42}

(如果显示整个g的值,则会更长,因为如果缺少,exec和eval会自动添加内置模块的__builtins__到全局变量)。

在Python 2中,exec语句的官方语法实际上是globals,locals中的exec代码,如

>>> 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对同一代码的重复调用。mode参数控制编译函数接受的代码片段的类型以及它产生的字节码的类型。 选择是'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 except,else 和 finally 块被视为是一个单独的语句。

在 “single”中包含2个顶级语句的源代码片段是错误的,除了在Python 2中有一个错误,有时候允许代码中有多个顶层语句; 只有第一个是编译的; 其余的被忽略:

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

这对于开发交互式Python shell非常有用。 但是,即使您 eval 生成的代码,表达式的值也不会返回。

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

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

虽然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'模式compile成代码对象,然后你可以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]]

不像大多数Python 2-3移植指南似乎表明,在CPython 2 EXEC语句也可以使用语法,看起来就像在Python 3 exec函数调用。原因是,Python 0.9.9有exec(代码、全局变量、局部变量)内置函数!在Python 1发布之前,内置的函数被替换了。

因为它是可取的不是Python 0.9.9打破向后兼容性,Guido van Rossum说1993的兼容性的问题:如果代码是一个元组长度为2或3,与全球和当地人不传递给exec语句否则,代码将被解释为如果元组的第二元和第三元的全局变量和当地人的分别。即使在Python 1.4文档(最早可用的在线版本)中也没有提到兼容性破解程序,因此许多移植指南和工具的作者都不知道,直到2012年11月再次被证明:

第一表达式也可以是长度为2或3的元组。在这种情况下,必须省略可选部件。 exec(expr, globals) 相当于执行于全局,而形式exec(expr,全局变量、当地人)相当于执行于全局,当地人。执行器的元组形式提供了与Python 3的兼容性,在这里Python是一个函数而不是一个语句。

是的,在当前的2.7,是轻易被提到的向前兼容性选项(为什么迷惑人,都是一个向后兼容性选项),当它实际上已经有二十多年的向后兼容性。

因此,执行是Python 1和Python 2的声明,并在Python 3和Python 0.9.9内置函数,

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

42

是的,在当前的2.7,是轻易被提到的向前兼容性选项(为什么迷惑人,都是一个向后兼容性选项),当它实际上已经有二十多年的向后兼容性。

因此,执行是Python 1和Python 2的声明,并在Python 3和Python 0.9.9内置函数,

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:

哪一种模式可能是某人实际使用过的,虽然不太可能;

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

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

这是列表解析滥用(使用循环取反!)。

热门问答

腾讯云广州一区DNS变更,需要怎么操作?

思潮澎湃轻描淡写的生活,但思潮澎湃
推荐
我也收到相关的通知了,这里分享下~ 2019年1月31日,腾讯云将对广州地区旧的基础网络DNS服务器(10.225.30.181、10.225.30.223)进行下线。在此期间,腾讯云提供最新的DNS服务器供您更新使用。 我们建议您尽快将DNS服务器配置进行更新,并且我们为您提供...... 展开详请

快照容量与费用的比例?如何关闭停用?

帅的惊动我国计算机大神
推荐已采纳
快照已于2019年1月22日0时启动正式商业化进程,商业化后所有存量快照和新产生的快照将根据快照使用的存储容量进行收费。 在快照商业化后,腾讯云仍旧会在国内主要地域为用户提供一定量的免费额度。免费额度策略如下: 免费额度覆盖范围为中国大陆地域,中国香港及海外地域暂无免费快照额...... 展开详请

React项目的try_files机制,在COS上怎么配置?

galenye

腾讯 · 工程师 (已认证)

对象存储专业搬砖工
推荐
COS的静态网站可以设置默认索引,你这里应该是想实现react-router spa场景下刷新浏览器时,不希望报404的场景吧 可以在COS静态网站这设置一个错误文档的默认索引来实现类似try_files的功能 image.png ... 展开详请

用户主动向云服务器的号码发送短信(不是回复),该条消息能否回调给业务服务器?

推荐

您好,主动上行需配置专属上行码号,月发送量大于300万条可申请配置。未配置专属上行码号用户可先下发短信后用户回复。感谢您对腾讯云短信的支持。

语音短信,怎么才能买到深圳的号码?

推荐已采纳

您好,语音号码受运营商监管管控使用,运营商所提供的号码是专门的用途使用,当前没有深圳号码,可以关注号码池的号码状态,谢谢。

请教关于云服务的运维升级的问题?

Eli Qiao

腾讯 · 高级工程师 (已认证)

腾讯云CVM后台高级研发工程师
推荐
关于云服务的运维升级的几个问题: 1 IaaS 1.1 用户购买了IaaS,比如一个虚机;云厂商在云的运维中(例如,升级服务器),是否会升级&迁移用户的虚机到新的硬件上面;还是保留用户的虚机在老的硬件上不动,直到用户自己调整? ---- 看服务器要如何升级了,有可能迁移走,有可能...... 展开详请

所属标签

扫码关注云+社区