eval、exec 和 compile 之间有什么区别?

2024-11-29 08:41:00
admin
原创
295
摘要:问题描述:我一直在研究 Python 代码的动态评估,并遇到了eval()和compile()函数以及exec语句。eval有人可以解释一下和之间的区别exec,以及不同的模式如何compile()适应吗?解决方案 1:简短回答,或者 TL;DR基本上,eval用于评估单个动态生成的 Python 表达式,并...

问题描述:

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

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


解决方案 1:

简短回答,或者 TL;DR

基本上,eval用于评估单个动态生成的 Python 表达式,并且仅exec用于执行动态生成的 Python 代码以获得其副作用。

eval并且exec有以下两个区别:

  1. eval只接受单个表达式exec可以采用包含 Python 语句的代码块:循环、、try: except:以及class函数/方法def初始化等等。

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

a_variable = (anything you can put within these parentheses is an expression)
  1. eval 返回给定表达式的exec,而忽略其代码的返回值,并始终返回None(在 Python 2 中它是一个语句并且不能用作表达式,因此它实际上没有返回任何东西)。

在 1.0 - 2.7 版本中,exec有一个声明,因为 CPython 需要为函数生成一种不同类型的代码对象,用于exec函数内部的副作用。

在 Python 3 中,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

compilein模式'exec'将任意数量的语句编译为隐式始终返回的字节码None,而 in'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`

>>> 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')这才是真正的差异所在。

如果将一个code对象(包含 Python字节码)传递给execeval它们的行为完全相同,除了忽略exec返回值,仍然始终返回。因此,如果您之前只是将其转换为字节码而不是将其作为字符串传递,None则可以使用它eval来执行具有语句的某些内容:compile

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

即使编译后的代码包含语句,它也能正常工作。它仍然返回None,因为这是从返回的代码对象的返回值compile

在模式下(如果传入字符串,则函数'eval'也是如此),如果源代码包含语句或单个表达式以外的任何其他内容,则会引发异常:eval`compile`

>>> 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 中为语句)用于执行动态创建的语句或程序:

>>> 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对象或包含 Python 字节码的对象来运行。unicode`bytes*code`*

如果将包含源代码的 //str传递给,其行为等效于:unicode`bytes`exec

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

并且eval类似地表现得等同于:

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

由于所有表达式都可以在 Python 中用作语句(Expr在 Python抽象语法中,这些表达式称为节点;反之则不然),因此,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 = 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 个额外的位置参数 -globalslocals- 它们是代码看到的全局和局部变量范围。它们默认为调用或的范围内的globals()和,但任何字典都可以用于和任何(当然包括)。它们不仅可用于限制/修改代码看到的变量,还经常用于捕获被调用代码创建的变量:locals()`execevalglobalsmappinglocalsdictexec`

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

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

exec在 Python 2 中,该语句的官方语法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`modecompile'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带有exceptelsefinally块的循环被视为单个语句。

包含 2 个顶级语句的源片段对于 来说是错误的'single',但在 Python 2 中有一个错误,有时允许代码中有多个顶级语句;只有第一个会被编译;其余的都会被忽略:

在 Python 2.7.8 中:

>>> exec(compile('a = 5
a = 6', '<string>', 'single'))
>>> a
5

在 Python 3.4.2 中:

>>> exec(compile('a = 5
a = 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其实就来自于compile功能和模式。


除了将源代码编译为字节码之外,compile还支持将抽象语法树(Python 代码的解析树)编译为code对象;以及将源代码编译为抽象语法树(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

compile将它以'exec'模式放入一个code对象中,然后您就可以了evaleval函数将返回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

execPython 2 和 Python 3 之间的语法差异

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

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

由于不希望破坏与 Python 0.9.9 的向后兼容性,Guido van Rossum 在 1993 年添加了一个兼容性 hack:如果code是长度为 2 或 3 的元组,且globalslocals未传递到exec语句中,code则将解释为元组的第 2 个和第 3 个元素分别是和globalslocals兼容性 hack 甚至在Python 1.4 文档(最早可在线获得的版本)中也没有提及;因此许多移植指南和工具的作者都不知道,直到2012 年 11 月再次记录它:

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

是的,在 CPython 2.7 中,它被方便地称为向前兼容选项(为什么要让人们混淆是否存在向后兼容选项),而实际上它已经存在了二十年的向后兼容

因此,whileexec在 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改用循环!)。

解决方案 2:

  1. exec不是表达式:在 Python 2.x 中是语句,在 Python 3.x 中是函数。它会编译并立即评估字符串中包含的语句或语句集。示例:

 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)
print(6)')  # prints 5{newline}6.
 exec('if True: print(6)')  # prints 6.
 exec('5')                 # does nothing and returns nothing.
  1. eval是一个内置函数(不是语句),它计算表达式的值并返回表达式产生的值。例如:

 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.
  1. compile`exec是and的低级版本eval`。它不会执行或评估您的语句或表达式,而是返回可以执行此操作的代码对象。模式如下:

  2. compile(string, '', 'eval')返回执行完 后将执行的代码对象eval(string)。请注意,您不能在此模式下使用语句;只有(单个)表达式有效。

  3. compile(string, '', 'exec')返回执行完 后将执行的代码对象exec(string)。您可以在此处使用任意数量的语句。

  4. compile(string, '', 'single')类似于模式exec,但只需要一个表达式/语句,例如compile('a=1 if 1 else 3', 'myf', mode='single')

解决方案 3:

exec 用于语句并且不返回任何内容。eval 用于表达式并返回表达式的值。

表达式表示“某事”,而语句表示“做某事”。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用