Python内置函数“compile”的作用是什么?

52

今天我发现了一个内置函数compile。虽然我读了文档,但仍然不理解它的用法或适用范围。请有人能举个例子来解释一下这个函数的用途吗?如果有例子我将非常感激。

从文档中可以看到,该函数需要一些参数,如下所示。

compile(source, filename, mode[, flags[, dont_inherit]])
4个回答

66

这不是常用的函数。当您有Python源代码的字符串形式,并且想将其转换为Python代码对象以便保留和使用时,可以使用该函数。以下是一个简单的例子:

>>> codeobj = compile('x = 2\nprint "X is", x', 'fakemodule', 'exec')
>>> exec(codeobj)
X is 2

基本上,代码对象将一个字符串转换为一个对象,您可以稍后调用exec来运行字符串中的源代码。(这适用于“ exec”模式;如果字符串包含单个表达式的代码,则“ eval”模式允许使用eval。)这不是常见的任务,这就是为什么您可能永远不会遇到需要它的情况。

它的主要用途是在元编程或嵌入式环境中。例如,如果您有一个Python程序,允许用户使用自定义Python代码对其行为进行脚本化,您可以使用compileexec来存储和执行这些用户定义的脚本。

compile很少使用的另一个原因是,像execeval及其同类一样,compile是一个潜在的安全漏洞。如果您以字符串形式接收用户代码并将其编译,然后稍后执行它,那么您可能正在运行不安全的代码。(例如,想象一下,在上面的示例中,代码是formatYourHardDrive()而不是print x。)


如果我有两个文件的字符串:py1.pypy2.py(假设我从数据库单元格中获取这些字符串,用户通过浏览器界面选择这些文件上传),其中py1.py调用在py2.py中编写的函数。我该如何处理这种情况? - Mahesha999
1
@Mahesha999:可能有一种方法,但不是简单的方法。这绝对超出了在评论中讨论的范围。你可以尝试将其作为一个单独的问题来提问。 - BrenBarn

21

compileexeceval 的低级版本。它不执行或评估您的语句或表达式,而是返回一个可以执行它们的代码对象。模式如下:

  1. compile(string, '', 'eval') 返回的代码对象与您执行 eval(string) 时将要执行的代码对象相同。请注意,您不能在此模式中使用语句;仅一个(单一)表达式是有效的。用于单个表达式。
  2. compile(string, '', 'exec') 返回的代码对象与您执行 exec(string) 时将要执行的代码对象相同。您可以在此处使用任意数量的语句。用于整个模块。
  3. compile(string, '', 'single') 类似于 exec 模式,但它将忽略除第一条语句以外的所有内容。请注意,带有其结果的 if/else 语句被视为单个语句。用于一个单独的语句。

请查看文档。还有一个很棒(简化版)的解释在这里,其中有一个使用示例。


6
我更喜欢你称赞我的解释很棒 ;) - JoeQuery
1
@JoeQuery 哇,你好!自从我开始学习以来,我从你的东西中学到了很多!谢谢! - anon582847382
1
请参考@max-shawabkeh在此处的被接受的答案:https://dev59.com/t3E95IYBdhLWcg3wp_gg - x29a
简化的解释非常棒。 - Davos

6
你具体不理解什么?文档解释了它将会:
将源代码编译为代码或AST对象。 代码对象可以通过exec语句执行,或通过调用eval()进行评估。源可以是Unicode字符串,Latin-1编码的字符串或AST对象。有关如何使用AST对象的信息,请参阅ast模块文档。
因此,它接收Python代码并返回其中之一: exec将执行Python代码 eval将评估表达式,这比exec功能少 ast允许您浏览代码生成的抽象语法树

1
+1 为提出 ASTs 而赞叹 - 这正是编译器实际使用的内容[1]。 [1] - http://svn.python.org/view/python/trunk/Demo/parser/unparse.py?view=markup - eatonphil

0
编译一个ast树
使用compile的另一个酷炫功能是编译一个ast树,而不仅仅是代码字符串。这对于AI代码生成可能很有意思,因为生成ast树可能比生成字符串更有趣,毕竟理解程序首先意味着理解AST树。
以下是一个示例:
import ast
from ast import *

myast = ast.parse('''def inc(i):
    return i + 1

print(inc(2))
''')
# print(ast.dump(myast, indent=2))
print(ast.unparse(myast))
code = compile(myast, 'mymodule', 'exec')
exec(code)
print()

myast2 = ast.fix_missing_locations(Module(
  body=[
    FunctionDef(
      name='inc',
      args=arguments(
        posonlyargs=[],
        args=[
          arg(arg='i')],
        kwonlyargs=[],
        kw_defaults=[],
        defaults=[]),
      body=[
        Return(
          value=BinOp(
            left=Name(id='i', ctx=Load()),
            op=Add(),
            right=Constant(value=1)))],
      decorator_list=[]),
    Expr(
      value=Call(
        func=Name(id='print', ctx=Load()),
        args=[
          Call(
            func=Name(id='inc', ctx=Load()),
            args=[
              Constant(value=2)],
            keywords=[])],
        keywords=[]))],
  type_ignores=[]))
# print(ast.dump(myast2, indent=2))
print(ast.unparse(myast))
code2 = compile(myast2, 'mymodule', 'exec')
exec(code2)

输出:

def inc(i):
    return i + 1
print(inc(2))
3

def inc(i):
    return i + 1
print(inc(2))
3

所以我们可以看到,使用ast.*对象,我们能够以与来自常规Python代码字符串的Python代码相匹配的方式构建完全基于ast树的Python代码。
检查code对象
要了解code包含什么以及如何检查它,请参阅:在此TypeError消息中提到的“code对象”是什么? 在Python 3.11.4、Ubuntu 23.04上进行了测试。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接