Python解释器浅谈

Python解释器浅谈 上一篇文章我们谈了编译器这一章我来谈谈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而不是别的什么如果了解的多一点的同学可能就知道解释器有好多种大家可能见过CPythonJython等。我们默认用的都是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搜索模块或者包的规则1python解释器会从当前目录去找如果找到那么搜索停止2搜索环境变量PYTHONPATH定义的目录列表如果找到那么搜索停止3搜索ptyhon的标准库路径就是python的安装路径的lib目录,如果找到对应的模块或者包则搜索停止4python第三方库的路径就是我们通过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解释器就说到这里了希望对大家有所帮助。