关键字:检测程序的import语句,编译.pyc字节码文件,判断旧.pyc文件时间以更新内容,提前加载以节省资源,编译型语言和解释型语言的理解(内容来源于网易云课堂王顺子老师)

python程序的运行机制可以概括成一个流程图:


在网上看很多教程都说python是个解释型的语言,但是实际上python也有编译型语言的特点:存在把整体代码一次性编译成更低级的语言的步骤。

图片解释:从左上角开始,假设我们想在命令行中运行一个名为a.py的文件,输入python a.py回车后,python解释器就会先检测a.py的源码中是否有import语句,即是否借用了其他文件的代码,如果没有借用,那么直接把a.py文件一次性编译成一个.pyc文件存放在内存中,然后再一条一条的开始解释这个.pyc的代码,注意这个时候在文件夹(硬盘)下是看不到这个.pyc文件的,在运行完成后不会被永久保存,即不会被持久化。(图中最左边那条,比较简单的线)

那如果这个a.py文件需要用到其他文件(b.py)的代码,比如import b,那么python就会去检测当前文件夹下是否有已经编译好的b.pyc文件。如果当前文件夹下没有,那么python解释器就会认为这个b.py文件从没有被编译为.pyc字节码文件过,就会立马把这个b.py文件编译为b.pyc文件保存在当前文件夹下(python3.8是在当前文件夹下创建了一个_pycache_目录,再在这个目录下创建一个名为b.cpython-38.pyc文件),同时记录创建这个字节码文件的时间(伏笔),再就根据b.pyc和a.pyc去执行。注意这个时候解释器是把b.pyc文件永久保存在磁盘中了,因为对于解释器来说,这个a.py文件好比是一系列操作,而这个b.py文件则是完成这一系列操作中必须要使用的工具,把b编译好之后的结果b.pyc保存下来就是有意义的(可以复用从而节省资源),而把a.pyc保存下来就没什么意义。

但是这会遇到一个问题:如果我升级了b这个工具,即对b.py文件做了一定改动,那这个升级的内容也会在python解释器的考虑范围之中。这个实现起来就是靠上次编译时记录的时间(上次创建b.pyc的时间),和这次对b.py改动的时间,做一个对比,一下就能看到这个b.pyc是不是最新的b.py文件编译而成。如果是旧的b.pyc,那就重新编译并覆盖原来的字节码文件。于是就有了上面那个流程图。

其他说明:在python3.7和3.8的版本,如果把b.py文件删除,只保留b.pyc文件,那这个a.py文件就不可以运行了(在IDLE中报错说找不到b这个模块),但是在之前的老版本是可以运行的,即只要有b.pyc就可以(具体是多老的版本未去考证)。

运行前:


运行后:


用ultraedit尝试打开这个字节码文件,看到里面确实是更低级的语言:


总结:(1)python并不是单纯的解释型语言,整体编译时可能会产生.pyc文件,也称做字节码。

(2)在编译时,源码中的import语句会导致解释器生成永久保存的.pyc文件,可复用以节省资源。

(3)在3.6版本,模块.pyc文件甚至可以代替模块.py文件,达到对源码保密的目的。