☰
Python解释器浅谈
2026/10/6 6:12:39 网站建设 项目流程

上一篇文章我们谈了编译器,这一章我来谈谈python的解释器。Python解释器是python代码执行的核心组件,它将python代码转换为机器可以执行的二进制命令。

其实python的编译执行的过程和java是很相似的,下面我们简单说一下从代码到执行的一个过程。先看下面的代码:

import copy #引入copy包 #定义了一个list列表 list = ['red', 'green', 'blue', 'yellow', 'white', 'black'] #调用copy包中的copy函数,复制出一个新的list a = copy.copy(list) #打印a的id print(id(a)) #打印list的id ptint(id(list))

这段代码是利用copy包中的copy函数重新复出一个新的list。

如果我们执行这块代码,python解释器会做出如下的动作:

1,首先是做词法分析和语法分析,这个步骤简单来说就是检测你写的代码是否正确。具体细节这里不再讨论了。


2,编译:

这个java的编译过程很像,就是将python的源码编译成字节码,这个字节码类似于java的字节码,都是一种中间代码,都是二进制形式的文件,以.pyc后缀结束。这个.pyc文件是与机器无关的,可以用这个文件在任何平台上执行。我们知道java虚拟机实现了平台无关性,对与python来讲,我们将执行.pyc的叫python虚拟机。python的编译和执行结合的很紧密,所以表面上来看,似乎没有像java那样特别的强调java虚拟机,但是python确实也是有python虚拟机。另外python的这个.pcy文件会有加速执行的作用,如果你第一次运行上面这段代码,则会生成.pyc文件,如果第二次运行,如果该文件没有改变过,那么python解释器会直接用原来的.pyc文件执行,并不会再次生成新的.pyc文件。这样做的好处是,省去了编译的时间,加速代码执行。

另外,观察仔细的同学可能觉得这个.pyc中的c有点奇怪,为啥是c而不是别的什么?如果了解的多一点的同学可能就知道,解释器有好多种,大家可能见过CPython,Jython等。我们默认用的都是CPython的解释器,所以生成的字节码是.pyc结尾的,那么再联想一下,用Jython解释器编译生成的字节码会不会是以.pyj结尾的呢?可惜,答案不是想我们想象的那样,而是以.class为后缀结尾的。这个后缀名是不是有点熟悉,对了就是java的字节码文件名后缀,这样似乎也说的通,记得上一章我们说过java虚拟机实现了平台无关性和语言无关性,通Jython生成的python字节码,可以在java虚拟机上运行,所以为了保持一致性,用.class做后缀名也就顺理成章了。

3,解释执行:

这一块是我们大多数人直观感受的一步,相信很多同学盯着控制台输出在python代码的解释执行的时候,看着自己的代码被正确的执行,是一件很有成就感的事情,而且python允许你修改一下立马就可以验证,这是python语言极其友好的地方。

包或者模块的导入:当python解释器开始执行上面代码的时候,首先它碰到了第一行代码import copy,这个代码是导入这个模块,python解释器看到这个命令后,第一件事情是去查看一下内存中是否已经导入了这个模块了,如果已经导入了,便不会再次导入。所以利于这个import的这个特点,我们可以实现单例模式。我们可以将一个对象放入一个模块中,然后每次使用的时候,我们直接用import来导入这个模块,因为这个模块在内存中只存在一份,所以这个模块中的对象也只有一个了,这样就实现了单例模式。当然单例模式的实现有多种方式,这个是最简单直白的方式。python的这个导入包的模式,其实很像动态链接库,只有在需要的时候再进行导入。

假如此时发现这个模块或者包没有导入呢?如果没有导入,那么python解释器就会尝试导入这个包或者模块,那如何去找这个包或者模块呢?

在回答这个问题之前,可能有的同学已经感受到了我在上面频繁的说包或者模块,那么这个包和模块有什么不同呢?首先说模块,一个python文件就可以认为是一个模块,一个包是一个目录,这个目录里面可以包含多个python文件,一般情况下这个目录下面会有一个__init__.py文件,用来说明这个目录是一个包。那么总结来讲,就是一个包里面可以包含一个或者多个模块或者包。这个文件夹的名字就是包的名字。那么这个__init__.py有什么作用呢?我们可以理解这个__init__.py来表明这个文件夹是个包(python3.3之后这个文件可以没有)。关于包的话题,有很多细节的内容,这里就先不展开讨论了。

接下来我们来大致谈一下python搜索模块或者包的规则:

1,python解释器会从当前目录去找,如果找到,那么搜索停止

2,搜索环境变量PYTHONPATH定义的目录列表,如果找到,那么搜索停止

3,搜索ptyhon的标准库路径,就是python的安装路径的lib目录,如果找到对应的模块或者包,则搜索停止

4,python第三方库的路径,就是我们通过pip安装的库的路径,位于site-packages目录

上面的四个地方基本上是我们搜索包的路径,我们也可以通过sys.path来查看,如下代码所示:

import sys print(sys.path) #-----------------下面为输出结果-------------------- ['/Users/XXX/PycharmProjects/pythonProject', '/Users/XXX/PycharmProjects/pythonProject', '/Library/Frameworks/Python.framework/Versions/3.12/lib/python312.zip', '/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12', '/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages']

这个路径来看,第一条和第二条其实就是当前目录,倒数一条和第二条是之前我们说的第三方库和标准库,具体的名字可能和python的版本有关系。因为我这个机器上并没有设置环境变量,所以并没有PYTHONPATH的内容。

如果你想引用的包或者模块都不在所列出的路径下面,那么你有两个选择:将包或者模块放到上面的路径中,或者在imoprt这个包或者模块之前,通过添加代码sys.path.add("path"),将对应的路径添加到系统路径中,其中path就是你要import的包或者模块所在的目录。

接下来包或者模块引用成功,解释器继续执行代码,如果不出错,那么最后执行的结果是成功的。我们看看执行代码的输出:

4300310912 4300839488

这段代码我们先定义了一个列表,然后用copy函数将这个列表重新拷贝了一份,最后我们将这两个列表的id打印了出来,我们发现这两个列表的id是不同的,说明通过copy函数确实生成了一个新的列表。这里的id我们可以认为是这两个列表的唯一标识符,在CPython的解释环境中,也可以认为是地址,地址不同,肯定是两个不同的对象。关于copy函数,这里叫浅拷贝,其实这个模块还有个函数,叫deepcopy,这个函数叫深拷贝。对于我上面列出的代码,调用深拷贝和浅拷贝的效果是一样的,没什么区别。但是如果列表中成员的类型不是基本类型的话,深拷贝和浅拷贝的结果就不同了,这个话题先不在这里讨论了,后续可以单开一章来讲这个话题。

至此,我们python解释器就说到这里了,希望对大家有所帮助。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询