码匠教育:一份 Python 脚本,从写完到执行经历了哪些步骤 许多人对于纯解释型语言所形成的印象是, 认为书写完毕的代码就是完全交由解释器以逐行读取的方式来直接执行。但实际上这并非真实情况, 一份脚本文件从编写完成到最终输出运行结果, 其间会经历若干不同的处理环节, 并且甚至也包含了编译这样的步骤, 只是因为整个处理流程对使用者来说是完全透明的, 所以导致很少有人去关注这个问题。当你在操作界面里面按下那个开始运行的按钮之后, 解释器这一方首先要去做的一个动作叫做词法分析。这个分析的过程就是针对你写好的那一整块代码内容执行拆开处理的操作, 把它分成很多个单独存在的最小的语法单位。这些语法单位具体可以是一些关键字、一些变量名称、还有运算符以及括号、甚至是字符串之类的东西。我们可以这么来理解, 这就像是在拆解一句话一样把整句话拆散变成一个个独立的词汇, 从而能够确认出来每一个部分到底属于什么样的类型。在这一步骤进行的过程中, 还要负责把代码文本里面存在的注释内容、那些多余的空格字符以及换行符号给过滤掉。这样做最终能够保留下来的就只有那些对语法意义有效的基本元素成分。在这一步骤里面, 它只去做拆分这样一个动作, 并且它不会去对逻辑的正确性或者是错误情况进行判断, 即便是所有的代码都是完全错误的, 同样也能够完成拆分的操作。在词法分析做完之后接着做语法分析这个过程, 解释器会把那些已经拆分好的语法单元, 按照语法规则去组合, 最后组合成一棵抽象语法树, 在这一部分的操作里头, 程序要去检查代码里面存在没有语法层面的错误, 像是缩进的方式不对、括号不能相互匹配、还有关键字的使用情况有错误, 这些类型的问题都是在当前的这个阶段里面被找出来的, 一旦检测出来确实有语法错误这样的状况出现, 整个程序就会直接抛出语法相关的异常信息, 同时立即终止后续的代码执行动作, 绝对不会再继续往后进行任何操作。这也是为什么程序在刚开始执行运行操作的时候, 就会立刻把一些语法方面的错误给报出来, 而并不是等到代码真正运行到了那特定的一行代码的位置上之后, 才再去出现那个报错的情况。语法检查通过了, 紧接着就进入了编译阶段。在解释器这个过程中, 它会把这个语法树编译成为字节码。这种字节码就是一种中间格式的指令。这种指令并不是cpu能直接运行的机器码类型。它是专门给虚拟机来使用的设计。要是导入的是外部模块的话, 它有时候会把编译好了的字节码保存成一个.pyc文件, 把这些文件放在相应的文件夹里面, 等到下次运行的时候去调用, 如果源代码本身没有进行过任何修改, 系统就会直接去读取那个.pyc文件, 这样做的好处就是不用重新进行编译操作, 从而节省了启动所需要的时间, 很多人在看到项目里面的.pyc文件时会感到好奇, 想知道这些文件具体起到什么作用, 实际上它们的作用就是作为编译之后所产生的字节码缓存。等编译的工作全部做完以后, 这才算是真正进入了去执行的那个阶段, 接着就由那个叫虚拟机的东西来把那些字节码给执行了。这个虚拟机会一条接一条地把你写的字节码指令给读取进来, 然后把它每一条都转成能对应得上当前操作系统的那些最底层的操作方式, 就这样才开始真真正正地着手去处理数据、再去执行相关的逻辑、最后再把结果给输出来。在整个这么个过程当中, 虚拟机负责去把那些不同操作系统之间存在的差异给屏蔽掉, 因此也就导致了同一个脚本文件, 能够在Linux系统上也能成功运行这件事情的发生。你要是弄清楚了这一步是怎么回事的话, 你就会真正明白过来, 原来它是属于那种先把代码编译成别的格式、然后再去解释的类型的一个程序方式, 它根本就没有直接去对着源代码去做解释这一个动作, 你既然知道了这一点的话, 你就能够理解得通为什么它跑起来的速度会比较慢, 比起像C语言这样的需要专门编译的那种类型的程序来讲是这样的一种状况, 因为那个生成的字节码是不能够直接被电脑里面的中央处理器拿去执行的一个东西, 所以中间必须要经过一个叫虚拟机的那个程序去做转换和处理的这么一下事情, 除此之外还要加上在执行运行过程中那些动态的处理行为存在, 自然就会造成性能上面会有一部分损失掉的现象。整体执行的时候, 对用户来说是完全透明的, 用户根本不需要去手动执行任何一步操作。但是, 如果大家去了解一下背后运行流程, 就能帮助我们更加深入地把各种特性也给弄明白, 一旦碰到报错情况的时候, 也能更清楚地去判断问题具体是出在了哪一个阶段, 这样排查起来也就更有方向感了。