Python可执行文件如何解析和执行脚本?

时间:2014-06-22 23:47:31

标签: python python-2.7 ipython

假设我有以下脚本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()或之前?

  6. 时,Python会在什么时候意识到这一点

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

2 个答案:

答案 0 :(得分:4)

有关modules教程的部分中的一些信息,但我认为文档没有完整的参考资料。所以,这里发生了什么。

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

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

这是第一阶段:编译。 Python只有一个其他阶段,实际上是运行代码。除了deflambda 中包含的语句之外,模块中的每个语句都按顺序执行。这意味着import会在运行时发生,无论您将它们放在模块中的哪个位置。这是将它们全部放在首位的良好卫生的部分原因。 defclass也是如此:这些只是创建特定类型对象的语句,并且会像遇到其他任何内容一样执行。

这里唯一棘手的问题是阶段可能不止一次发生 - 例如,import仅在运行时执行,但如果您之前从未导入过该模块,那么它必须编译,现在你回到编译时。但是&#34;外面&#34;导入它仍然是运行时,这就是为什么你可以捕获SyntaxError抛出的import

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

  1. 在编译时。当您将其作为脚本运行时,或者将其作为模块导入时,或者使用compileall进行编译时,或者要求Python对其进行任何理解。实际上,这可以随时发生:如果您尝试在函数中导入此模块,那么在调用该函数时,您只能获得SyntaxError,这可能是您的程序的一半。

  2. 在执行foo期间,因为defclass只是创建一个新对象并将其指定给一个名称。但Python仍然知道如何来创建嵌套函数,因为它已经编译了其中的所有代码。

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

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

  5. 仅在执行foo()期间。 Python无法知道其他代码是否会在该点之前向模块添加do_things,甚至无法将my_library完全更改为其他对象。当你提出要求时,属性查找总是及时完成。

答案 1 :(得分:1)

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

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