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

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

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

我一直在研究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

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

热门问答

腾讯云 COS 怎么才能外链调用 m3u8 到别的网站播放?

滑稽园扛把子

Swoole · PHP开发工程师 (已认证)

As a PHP Developer
推荐
设置公有读私有写:当访问对象时,COS 读取到对象的权限为公有读,此时无论存储桶为何种权限,对象都可以被直接下载 设置步骤 登录 对象存储控制台,选择左侧菜单栏【存储桶列表】,进入存储桶列表页面。单击需要修改对象权限的对应存储桶,进入存储桶。 📷 找到需要设置权限的对象(如 e...... 展开详请

Ubuntu搭建的WordPress如何修改php.ini?

滑稽园扛把子

Swoole · PHP开发工程师 (已认证)

As a PHP Developer
推荐
php新手很多不知道怎么查配置文件在哪,这里提供一个很简单的方法 使用 php -i 命令可以打印php的详细信息,可以把这堆东西输出一下 php -i > outputphp.txt,结合 grep 查找命令 php -i| grep php.ini 打印结果如下 Config...... 展开详请

归档存储采用的存储介质是什么, 安全可靠吗?

滑稽园扛把子

Swoole · PHP开发工程师 (已认证)

As a PHP Developer
推荐
归档存储主要是针对海量、重要且访问频率极低的非结构化数据进行长期的归档保存和备份管理。 在数据安全层面,归档存储提供数据锁定机制,防止数据被修改和删除,保障数据安全。 技术架构: image.png 与对象存储的差异 归档存储 CAS 是一项离线存储服务,不同于在线的对象存储 ...... 展开详请

在按官网手册排错后依然提示1004错误?

看你的代码好像是短信相关的代码,1004错误代表请求包解析失败,通常情况下是由于没有遵守 API 接口说明规范导致的。 建议您通过以下方式定位解决: 首先,要确认发送的请求是否是标准的 json 格式; 第二,检查是否有将单引号当做双引号使用(json 标准应该是双引号); 第...... 展开详请

redis数据库应该怎样连接???

滑稽园扛把子

Swoole · PHP开发工程师 (已认证)

As a PHP Developer
推荐
实例初始化完成后,连接腾讯云Redis时,需要输入设置的密码。主从版和集群版的连接示例如下 主从版连接示例 主从版支持2种格式 • 格式1,“实例id:密码”的格式类型,例如您的实例id是crs-bkuza6i3,设置的密码是abcd1234,则连接命令如下 redis-cli ...... 展开详请

如何使用holer实现从外网访问本地WEB应用?

Dingda

Dingda · 站长 (已认证)

多一些不为什么的坚持
推荐
解压holer软件 获取holer access key信息: 在holer官网上申请专属的holer access key或者使用开源社区上公开的access key信息。 启动holer服务: Windows系统平台: 打开CMD窗口进入可执行程序所在的目录下,执行命令:...... 展开详请

所属标签

扫码关注云+社区