【问题标题】:How does the Python executable parse and execute scripts? [closed]Python 可执行文件如何解析和执行脚本? [关闭]
【发布时间】:2014-06-22 23:47:31
【问题描述】:

假设我有以下脚本,test.py:

import my_library

bar = 12

def foo():
    nested_bar = 21

    my_library.do_things()

    def nested_foo():
        nested_bar += 11
        not_a_variable += 1
            {$ invalid_syntax

bar = 13
foo()
bar = 14

我很好奇当我运行python test.py 时到底发生了什么。显然 Python 不只是逐行读取程序 - 否则它不会在实际执行程序之前捕获语法错误。但这使得解释器的工作显得有些模糊。我想知道是否有人会帮助我解决问题。我特别想知道:

  1. Python 什么时候意识到第 13 行有语法错误?

  2. Python 在什么时候读取嵌套函数并将它们添加到foo 的范围内?

  3. 同理,Python 遇到函数foo 时如何将其添加到其命名空间,而不执行它?

  4. 假设 my_library 是无效导入。 Python 是否必须在执行任何其他命令之前引发 ImportError

  5. 假设 my_library 是一个有效的模块,但它没有函数 do_things。在foo() 执行期间或之前,Python 会在什么时候意识到这一点?

如果有人能指出有关 Python 如何解析和执行脚本的文档,我将不胜感激。

【问题讨论】:

  • 1.从来没有,因为第 5 行有语法错误 :)
  • 哈哈,这就是我没有仔细阅读我的程序而得到的。固定
  • 我认为这个问题一点也不宽泛——Eevee 的回答完全涵盖了它。我只是想知道解释器在解析/执行过程中采取的步骤。

标签: python python-2.7 ipython


【解决方案1】:

在教程的modules 部分中有一些信息,但我不认为文档对此有完整的参考。所以,这就是发生的事情。

当您第一次运行脚本导入模块时,Python 会将语法解析为 AST,然后将其编译为字节码。它还没有执行任何操作;它只是将您的代码编译为基于堆栈的小型机器的指令。这是捕获语法错误的地方。 (您可以在 ast 模块、token 模块、compile 内置模块、grammar reference 中看到所有这些内容,并散布在其他各个地方。)

您实际上可以独立于运行生成的代码来编译模块;这就是内置的compileall 方法所做的。

这就是第一阶段:编译。 Python 只有另一个阶段,即实际运行代码。模块中的每个语句,除了包含在 deflambda 中的语句,都按顺序执行。这意味着imports 发生在运行时,无论您碰巧将它们放在模块中的什么地方。这是将它们全部放在顶部的良好卫生习惯的部分原因。 defclass 也是如此:这些只是创建特定类型对象的语句,它们会在遇到时执行,就像其他任何事情一样。

这里唯一棘手的一点是这些阶段可能会发生不止一次——例如,import 仅在运行时执行,但如果您之前从未导入过该模块,则必须对其进行编译,并且现在你又回到了编译时间。但是“在导入之外”它仍然是运行时的,这就是为什么你可以捕捉到 import 抛出的 SyntaxError

无论如何,回答您的具体问题:

  1. 在编译时。当您将其作为脚本运行时,或者当您将其作为模块导入时,或者当您使用 compileall 编译它时,或者以其他方式要求 Python 理解它时。实际上,这种情况随时都可能发生:如果您尝试在函数中导入此模块,则在调用该函数时只会得到SyntaxError,这可能是您的程序进行到一半。

    李>
  2. foo的执行过程中,因为defclass只是创建了一个新的对象并给它赋值。但是 Python 仍然知道如何创建嵌套函数,因为它已经编译了其中的所有代码。

  3. 与将foo = lambda: 1 + 2 添加到命名空间而不执行它的方式相同。函数只是一个包含“代码”属性的对象——实际上只是一个 Python 字节码块。您可以将code 类型作为数据进行操作,因为它 数据,与执行它无关。尝试查看函数的.__code__,阅读the data model 的“代码对象”部分,或者甚至玩弄disassembler。 (您甚至可以使用exec 直接使用自定义局部变量和全局变量执行代码对象,或者更改函数使用的代码对象!)

  4. 是的,因为import 是一个普通的旧语句,与任何其他语句一样,按顺序执行。但是如果在import之前还有其他代码,那将首先运行。如果它在一个函数中,那么在该函数运行之前你不会得到错误。请注意,importdefclass 一样,只是一种奇特的赋值形式。

  5. 仅在执行foo() 期间。 Python 无法知道其他代码是否会在此之前将do_things 添加到您的模块中,或者甚至将my_library 完全更改为其他对象。属性查找总是在您要求时及时完成,而不是提前完成。

【讨论】:

  • 嘿@Eevee 快速提问。所以在编译期间,Python 必须进行词法解析,但它不执行任何操作?所以它只是“构建”稍后将执行的定义。我说的是function...所以在编译期间,python 会经过那里,检查所有语句(assignment 等),找出constantsvariables(在哪里加载它们,如果它们是全局的,本地的,基本上是它们的范围)并且几乎退出,然后......当您执行该函数(在不同的时间戳)时,它已经准备好该代码并且只是跳转到那里并自上而下?
  • @George 或多或少,是的。请注意,编译函数和创建函数是分开的步骤——在def 语句实际执行之前,函数对象并不存在,即使该函数的字节码已经生成。
【解决方案2】:

作为一般规则,python首先解析文件,将抽象语法树编译为字节码,然后尝试顺序执行。这意味着所有语句都是逐行执行的。因此,这意味着:

  1. 语法错误在解析时被捕获,在执行任何操作之前。如果您在脚本中添加一些副作用,例如创建一个文件,你会发现它永远不会被执行。
  2. 函数在定义后的范围内定义。如果您尝试在 def nested_foo() 之前调用 nested_foo,您会看到它会失败,因为此时尚未定义 nested_foo
  3. 同 2。
  4. 如果 python 无法导入库,其中 import 意味着它尝试执行模块,则它会失败并返回 ImportError
  5. 由于您在导入时没有尝试访问do_things(即您没有在进行from my_library import do_things),因此只有在您尝试调用foo() 时才会出现错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-11
    • 1970-01-01
    • 2011-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多