Python程序的执行过程原理(解释型语言和编译型语言)

Stella981
• 阅读 335

Python是一门解释型语言?
我初学Python时,听到的关于Python的第一句话就是Python是一门解释型语言,我就这样一直相信下去,直到发现.pyc文件的存在,如果真是解释型语言,那么生成的.pyc文件的是什么呢?c应该是compiled的缩写才对啊!
为了防止其他学习Python的人也被这句话误解,那么我们就在文中来澄清一下这个问题,并且把一些基础概念给理一理。

解释型语言和编译型语言
计算机是不能够识别高级语言的,所以当我们运行一个高级语言程序的时候,就需要一个“翻译机”来从事把高级语言转变成计算机能读懂的机器语言的过程。这个过程分成两类,第一种是编译,第二种是解释。
编译型语言在程序执行之前,先会通过编译器对程序执行一个编译的过程,把程序转变成集齐语言。运行时就不需要翻译,而直接执行就可以了,最典型的例子就是C语言。
解释型语言就没有这个编译过程,而是在程序运行的时候,通过解释器对程序逐行做出解释,然后直接运行,最典型的例子是Ruby。
通过以上的例子,我们可以来总结一下解释型语言和编译型语言的优缺点,因为编译型语言在程序运行之前就已经对程序做出了“翻译”,所以在运行时就少掉了“翻译”的过程,所以效率比较高。但是我们也不能一概而论,一些解释型语言也可以通过解释器的优化来在对程序做出翻译时对整个程序做出优化,从而在效率上超过编译型语言。
此外,随着Java等基于虚拟机的语言的兴起,我们又不能把语言纯粹的分成解释型和编译型这两种。
用Java来举例,Java首先是通过编译器编译成字节码文件,然后在运行时通过解释器给解释成机器文件。所以我们说Java是一种先编译后解释的语言。
再换成C#,C#首相是通过编译器将C#文件编译成IL文件,然后在通过CLR将IL文件编译成机器文件。所以我们说C#是一门纯编译语言,但是C#是一门需要二次编译的语言。同理也可以等效运用到基于.NET平台上的 其他语言。

Python到底属于哪一种类型?
其实Python和Java/C#一样,也是一门基于虚拟机的语言,我们先来从表面上简单的了解一下Python程序的运行过程吧。
当我们在命令行中输入python hello.py时,其实是激活了Python的“解释器”,告诉“解释器”:你要开始工作了。可是在“解释”之前,其实执行的第一项工作和Java一样,是编译。
熟悉Java的同学可以想象一下我们在命令行中如何执行一个Java的程序:
javac hello.java
java hello

只是我们在用eclipse之类的IDE时,将这两步给融合成了一步而已。其实Python也是一样的,当我们执行python hello.py时,它也一样执行了这么一个过程,所以我们应该这样来描述Python,Python是一门先编译后解释的语言。

简述Python中程序的运行过程
在说这个问题之前,我们先来说两个概念,PyCodeObject和pyc文件。
我们在硬盘上看到的pyc自然不必多说,而其实PyCodeObject则是Python编译器真正编译成的结果。我们先简单知道就可以了,继续向下看。
当Python程序运行时,编译的结果则是保存在位于内存中的PyCodeObject中,当Python程序运行结束时,Python解释器则将PyCodeObject写回到pyc文件中。
当Python程序第二次运行时,首先程序会在硬盘中寻找对应的pyc文件,如果找到,则直接载入,否则就重复上面的过程。
所以我们应该这样来定位PyCodeObject和pyc文件:pyc文件其实是PyCodeObject的一种持久化保存方式。

我们先来简单看两个例子
写一段简单的程序运行一下:
Python程序的执行过程原理(解释型语言和编译型语言)

运行结果:
Python程序的执行过程原理(解释型语言和编译型语言)

我们发现在运行完test.py这个程序后,当前路径下并没有生成相应的pyc文件。那是为什么呢?
我们再来做一个小测试,将test.py当做模块导入到test888.py文件中,然后在这个程序中运行print_hello这个程序:

 Python程序的执行过程原理(解释型语言和编译型语言)

Python程序的执行过程原理(解释型语言和编译型语言)

然后运行程序:

Python程序的执行过程原理(解释型语言和编译型语言)

我们可以发现运行完test888.py文件后在当前路径下出现了一个名叫__pycache__的文件夹,里面包含了一个pyc文件,下面我们来分析一下这个过程到底发生了什么。

pyc文件的目的
回想上面我们在分析编译型语言和解释型语言的优缺点时,编译型语言的优点在于,我们可以在程序运行时不用解释,而直接利用已经翻译过的文件。也就是说,我们之所以要把py文件编译成pyc文件,最大的优点在于我们在运行程序时,不需要重新对该模块进行再次解释。
所以,需要编译成pyc文件的应该是那些可以重用的模块,这于我们在设计类时是一样的目的。所以Python的解释器认为:只有import进来的模块,才是需要被重用的模块。
这个时候也许有人会有疑问,我的test.py不是也需要运行吗,虽然不是一个模块,但是以后我每次运行也可以节省时间啊!
OK,我们从实际情况出发,思考一下我们在什么时候才可能运行python xxx.py文件:
1. 执行测试时;
2. 开启一个Web进程时;
3. 执行一个脚本时。

我们来逐条分析,第一种情况就不多说了,这个时候哪怕所有的文件都没有pyc文件都是无所谓的。
第二种情况,试想一个web.py的程序,通常是这样执行的:

然后这个程序就类似于一个守护进程一样一直监视着8000端口,而一旦中断,只可能是程序被杀死或者其他的意外情况,那么你要做的是把整个Web服务重启,那么既然一直监视着,把PyCodeObject一直放在内存中就足够了,完全没有必要持久化到硬盘上。
再来看看最后一个情况,执行一个程序脚本,一个程序的主入口其实很类似于Web程序中的Controller,也就是说,他负责的应该是Model之间的调度,而不包含任何的主逻辑在内,只是负责把参数转来转去而已,那么如果做算法的同学可以知道,在一段算法脚本中,最容易改变的就是算法的各个参数,那么这个时候将它持久化成pyc文件就未免有些画蛇添足了。

所以我们可以这样理解Python解释器的意图,Python解释器只是把我们可能重用到的模块持久化成pyc文件。

pyc文件的过期时间
说完了pyc文件,可能有人会想到,每次Python解释器都把模块给持久化成pyc文件,那么当我的模块发生改变的时候,是不是都要手动的把之前的pyc文件remove掉呢?
当然Python的设计者是不会犯这样的错误的,这个过程其实取决于PyCodeObject是如何写入pyc文件中的。
我们仔细看一下Import模块的源码其实不难发现,它在写入pyc文件的时候,写了一个Long型变量,变量的内容则是文件的最近修改日期,同理,在pyc文件中,每次在载入之前都会检查一下py文件和pyc文件保存的最后修改日期,如果不一致则重新生成新的pyc文件。

总结
其实了解Python程序的执行过程对于大部分程序员来说意义都是不大的,那么真正有意义的是,我们可以从Python解释器的做法上学到一些处理问题的方式和方法:

在Python中判断是否生成pyc文件和我们在设计缓存系统时是一样的,我们可以仔细想想,到底什么是值得扔在缓存里面的,什么是不值得的。
在运行一个耗时的Python脚本时,我们如何能够做到稍微压榨一些程序的运行时间呢?就是将模块从主模块分开。(虽然往往这都不是瓶颈)
在设计一个软件系统时,重用和非重用的东西是不是也可以分开来对待,这是软件设计原则的重要部分。
在设计缓存系统(或者其他系统)时,我们如何来避免程序的过期,其实Python解释器为我们提供了一个特别常见而且有效的解决方案。

点赞
收藏
评论区
推荐文章
浅梦一笑 浅梦一笑
4个月前
初学 Python 需要安装哪些软件?超级实用,小白必看!
编程这个东西是真的奇妙。对于懂得的人来说,会觉得这个工具是多么的好用、有趣,而对于小白来说,就如同大山一样。其实这个都可以理解,大家都是这样过来的。那么接下来就说一下python相关的东西吧,并说一下我对编程的理解。本人也是小白一名,如有不对的地方,还请各位大神指出01名词解释:如果在编程方面接触的比较少,那么对于软件这一块,有几个名词一定要了解,比如开发环
技术小男生 技术小男生
4个月前
linux环境jdk环境变量配置
1:编辑系统配置文件vi/etc/profile2:按字母键i进入编辑模式,在最底部添加内容:JAVAHOME/opt/jdk1.8.0152CLASSPATH.:$JAVAHOME/lib/dt.jar:$JAVAHOME/lib/tools.jarPATH$JAVAHOME/bin:$PATH3:生效配置
光头强的博客 光头强的博客
4个月前
Java面向对象试题
1、请创建一个Animal动物类,要求有方法eat()方法,方法输出一条语句“吃东西”。创建一个接口A,接口里有一个抽象方法fly()。创建一个Bird类继承Animal类并实现接口A里的方法输出一条有语句“鸟儿飞翔”,重写eat()方法输出一条语句“鸟儿吃虫”。在Test类中向上转型创建b对象,调用eat方法。然后向下转型调用eat()方
刚刚好 刚刚好
4个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
小森森 小森森
4个月前
校园表白墙微信小程序V1.0 SayLove -基于微信云开发-一键快速搭建,开箱即用
后续会继续更新,敬请期待2.0全新版本欢迎添加左边的微信一起探讨!项目地址:(https://www.aliyun.com/activity/daily/bestoffer?userCodesskuuw5n)\2.Bug修复更新日历2.情侣脸功能大家不要使用了,现在阿里云的接口已经要收费了(土豪请随意),\\和注意
Karen110 Karen110
1年前
​一篇文章总结一下Python库中关于时间的常见操作
前言本次来总结一下关于Python时间的相关操作,有一个有趣的问题。如果你的业务用不到时间相关的操作,你的业务基本上会一直用不到。但是如果你的业务一旦用到了时间操作,你就会发现,淦,到处都是时间操作。。。所以思来想去,还是总结一下吧,本次会采用类型注解方式。time包importtime时间戳从1970年1月1日00:00:00标准时区诞生到现在
晴空闲云 晴空闲云
4个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
3个月前
快速入门|使用MemFire Cloud构建React Native应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
Stella981 Stella981
1年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
helloworld_28799839 helloworld_28799839
4个月前
常用知识整理
Javascript判断对象是否为空jsObject.keys(myObject).length0经常使用的三元运算我们经常遇到处理表格列状态字段如status的时候可以用到vue