1.1 前言
Python优越的特性这里就不展开了,相信读到这篇文章的大家也或多或少了解一点Python,作为使用最广泛的编程语言之一,Python可以应用于web开发,AI算法、爬虫应用等诸多方面。大多数新手教程会从基础开始讲起,逐步叫你开发出一个小的程序。如果你只是会用它来编点小程序,那其实和其他人比起来没啥特别的。但如果你抽丝剥茧,继续深挖Python解释器里面那些高大上的东西,比如:
- 列表、字典这些我们常用的东西,在Python里面是怎么实现的?
- 为啥Python的多线程不能同时用好多个CPU核,GIL又是什么?
- Python程序是怎么被转换成字节码,然后被一个叫虚拟机的东西执行的?
- Python的垃圾清理机制?
如果你了解这些,那去面试的时候肯定能让面试官刮目相看,而且你写的代码也会更厉害、更漂亮、更优雅。这就是为啥我们要去扒一扒Python解释器的源代码。实际上,Python的源码(解释器)是用C语言写的,所以我们叫它CPython。如果你想看得懂,最好有点C语言的基础。但是就算你没有,也没关系,在本专栏中我会用很简单的话来解释,还会加上详尽的注释,让你无需太多C的基础就可以领略到Python的优雅之处。
1.2 Python的解释器、编译器、虚拟机****
如果你了解 Java,那么应该知道 Java 是有编译器和虚拟机。只不过 Java 的编译器和虚拟机是分开的,而 Python 则是整合在一起的。也就是说:
Python 解释器 = Python 编译器 + Python 虚拟机。
那当我们完成一个Python脚本,执行Python xxx.py的时候,解释器执行 py 文件,都经历了哪些过程呢?
- Read file(读文件) : 将文件里的内容读取出来,所以从这个角度讲,Python xx.txt也是可行的,只要符合代码规范(例如缩进等)即可;
- Scanner(扫描): 读取py文件里面的内容,然后进行分词,将源代码切分成一个一个的token;
- Parser(解析): Python编译器对token进行语法解析,建立抽象语法树(AST: abstract syntax tree)
- Compiler(编译) :将AST编译成pycodeobject对象
- Code eval (执行):最终由Python虚拟机负责执行
在以上Python 的工作流程里,有几个重要角色:读文件(Read file)、扫描(Scanner)、解析(Parser)和编译(Compiler),这些活儿都是 Python 编译器来干的。但有个叫 Code Eval 的环节,是 Python 虚拟机来管的,理解起来图示如下。
那么现在来看一个初学者经常会疑惑的问题,也是Python的重点面试题:为什么说Python是解释型语言? 这里就得看看解释性语言和编译型语言的具体区别了:
编译型语言: 编译型语言是指使用专门的编译器,针对特定平台(操作系统)将某种高级语言源代码和一次性 “翻译” 成可被该平台硬件执行的机器码(包括机器指令和操作数),并包装成该平台所能识别的可执行程序的格式,这个转换过程称为编译(compile)。因为一次性 “翻译” 成机器码,编译生成的可执行程序可以脱离开发环境,在特定的平台上独立运行,所以通常运行效率较高。
解释型语言: 解释型语言是指使用专门的解释器对源程序逐行解释成特定平台的机器码,并立即执行的语言。解释型语言通常不会进行整体性的编译和连接处理,解释型语言相当于把编译型语言中的编译和解释过程混合到一起同时完成。每次执行解释型语言的程序都需要进行一次编译,所以效率通常较低,而且不能脱离解释器独立运行。
注意编译型是一次翻译所有源码,而解释型则是边读边执行。大家常说 Python 是解释型语言,就是边读代码边执行,但其实它也有个编译的步骤。源代码会被 Python 编译器先“翻译”成一种叫 PyCodeObject 的东西,这个东西就像是源代码的“简化版”,然后交给 Python 虚拟机去执行。这个PyCodeObject 到底是什么,我们后面会讲到。
那么问题来了,为啥要这么做呢?主要是为了快!编译的时候,像数字这样的常量会直接安排好位置,不用再每次执行时都重新算。还有,编译时就能发现你写的代码里有没有语法错误,早点告诉你改,免得运行时才出问题。所以,Python 的编译器和虚拟机合作无间,它们俩加起来就是 Python 解释器,负责把你的代码变成计算机能懂的语言,然后执行起来。
1.3 Python源码初探
压缩包下载下来之后解压,即可得到整个 CPython 工程项目,我们看看它长什么样子?
现在来解释一下每个目录的作用。
- Doc 目录 : 存储 Python 文档的源文件(.rst),用于编译之后生成官方文档。
- Grammar 目录 : 负责定义 Python 的语法规则。
- Include 目录 : 包含 Python 所有公开的头文件,这些文件定义了 Python 的 C API,在编写扩展模块和嵌入式开发时会用到。
- Lib 目录 : Python 的标准库,对于那些不影响性能的功能会用 Python 编写,然后放在 Lib 目录下面。
- Modules 目录 : Python 的内置库,这些库都是用 C 编写的,编译之后会内嵌在解释器里面。Modules 目录里面实现了大量和性能相关的模块,比如 sys、time、gc 等等,我们后续再聊。
- Objects 目录 : 包含 Python 内置数据结构的底层实现,像字典、列表、元组、函数等,底层实现都定义在 Objects 目录中。
- Parser 目录 : 负责 Python 编译器的具体实现,虽然 Python 是解释型语言,但也是要经过编译的。编译的结果为 PyCodeObject 对象,它里面包含了要执行的字节码,编译完之后会交给虚拟机执行。这也就是上文中我们提到的:Python 解释器 = Python 编译器 + Python 虚拟机。
- Python 目录 : Python 虚拟机的具体实现,字节码的执行、执行环境的管理等都在里面。
- Mac 目录 : 用于 Mac OS X 平台的特定工具和脚本。
- Misc 目录 : 包含各种杂项文件,如配置脚本、工具等。
- PC 目录 : 专为 Windows 平台编写的配置文件和特定扩展。
- PCbuild 目录 : 用于在 Windows 上编译 Python 的项目文件。
- Programs 目录 : 包含 Python 其它可执行文件(如 IDLE)的源代码。
- Tools 目录 : 包含用 Python 编写的各种脚本和工具,帮助开发和维护 Python。
- 其他: 一些工具脚本。
以上就是 CPython 的源码结构,对它有一个基本的认识有助于我们后续的源码学习。本专栏在后续介绍 Python 源码的时候,我们暂不涉及 Python 编译器的部分,也就是 Parser 目录里面的代码不做分析。而且编译这一过程也不是 Python 语言独有的,任何一门编程语言 都会涉及到编译。单独探究 Python 代码的编译过程没太大意义,感兴趣的小伙伴可以自行去学习编译原理,我们的重点是 Python 代码的编译结果也就是PyCodeObject 对象,以及虚拟机层面,Python是如何执行的?
1.4 小结
读完本章内容你应该有如下收获:
- Py文件的编译过程?(读文件、扫描、解析、编译、执行)
- Python解释器和编译器、虚拟机的关系?(解释器=编译器+虚拟机)
- Python是编译型还解释型语言,原因是什么?(解释型)
- Python源码的目录结构?
如果还对上面的问题有疑惑,请别忘了抽空回顾。如果您还想更精进Python,请关注我,后续我会分享更多技术相关事宜,我自己也维护一个Python学习者的交流群,群里氛围和谐,请感兴趣的小伙伴私聊我进群,大家一起学习进步~
作者介绍:SaviHuang ,清华本科毕业,先后历任多家公司技术合伙人;擅长围绕Python生态的相关技术开发;