python运行机制

解释器是一种让其他程序运行起来的程序,它是代码与机器的计算机硬件之间的软件逻辑层,Python解释器就是能够让Python程序在机器上执行的一套程序。 
当我们执行写好的Python代码时,Python解释器会执行两个步骤

1、把原始代码编译成字节码

编译后的字节码是特定于Python的一种表现形式,它不是二进制的机器码,需要进一步编译才能被机器执行,这也是Python代码无法运行的像C/C++ 一样快的原因。如果Python进程在机器上拥有写入权限,那么它将把程序的字节码保存为一个以.pyc 为扩展名的文件,如果Python无法在机器上写入字节码,那么字节码将会在内存中生成并在程序结束时自动丢弃。在构建程序的时候最好给Python赋上在计算机上写的权限,这样只要源代码没有改变,生成的.pyc文件可以重复利用,提高执行效率。

2、把编译好的字节码转发到Python虚拟机(PVM)中进行执行

PVM是 Python Virtual Machine的简称,它是Python的运行引擎,是Python系统的一部分,它是迭代运行字节码指令的一个大循环、一个接一个地完成操作。 

Python 解释器的几种实现版本

Python解释器有三种主要的实现方式,CPython、Jython和IronPython 三种实现方式 。

1、CPython

CPython 是标准的实现,其它的都是有特定目标的。 
CPython 是由C语言编写的,它是大多数Linux和Mac OS X机器预装的Python解释器,也是所有Python解释器中运行最快、最完整、最健全的。

2、Jython

Jython 是一种Python语言的替代实现方式,其目的是为了与Java编程语言集成,Jython 包含了Java类,这些类编译Python源代码、形成Java字节码,并将得到的字节码映射到Java虚拟机(JVM)上。因为Jython要比CPython 慢而且也不够健壮,它往往看作是一个主要面向寻找Java代码前端脚本语言的Java开发者的一个有趣的工具。 

3、IronPython

IronPython 设计的目的是让Python 程序可以与Windows 平台上的.NET 框架以及与之对应的Linux的上开源的Mono编写成的应用集成。

4.IPython

IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。

CPython用>>>作为提示符,而IPython用In [序号]:作为提示符。

5.PyPy

PyPy是另一个Python解释器,它的目标是执行速度。PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。

绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解PyPy和CPython的不同点

Psyco 实时编译器

psyco 系统不是Python的另一种实现方式,而是Python字节码执行模块的一个扩展组件,它可以让程序运行的更快。它是一个PVM的增强工具,这个工具收集并使用信息,在程序运行时,可以将部分程序的字节码转换成底层的真正的二进制机器代码,从而实现更快的执行速度。在开发个过程中,Psyco无需代码的修改或独立的编译步骤即可完成这一转换。 
概括地讲,就是当程序运行时,Psyco收集了正在传递过程中的对象的类别信息,这些信息可以用来裁剪对象的类型,从而生成高效的机器代码。机器代码一旦生成,就替代了对应的原始字节码,从而加快程序的整体执行速度。一些通过Psyco优化的Python代码的执行速度可以像编译好的C代码一样快。 
因为字节码的转换与程序运行同时发生,所以Pysco往往被看做是一个即时编译器(JIT)。实际上Psyco是一个专有的JIT编译器:它生成机器代码将数据类型精简至你程序实际上所使用的类型。 
Psyco是用纯Python的算法代码实现的。这点尤为重要,那些为了优化往往需要迁移到C的那部分代码,使用了Psyco后,这样的迁移就没有必要了。

作者:折木奉太郎
链接:https://www.zhihu.com/question/30296617/answer/112564303
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Python解释器将 源码转换为 字节码,然后再由 解释器来执行这些字节码。因此总的来说,它具有以下三条特性
  1. 源码距离底层更远(根据官方文档的解释。
  2. 运行时都需要生成字节码,交由虚拟机执行。(就是解释器。虚拟机具体实现了由switch-case语句构成的框架函数PyEval_EvalFrameEx,刚刚说的字节码就是这货执行的)
  3. 每次执行脚本,虚拟机总要多出加载和链接的流程。(所以呢,相比于编译型语言就有点慢了
 
虚拟机它是怎么执行脚本的
  1. 完成模块的加载和链接;
  2. 将源代码翻译为PyCodeObject对象(这货就是字节码),并将其写入内存当中(方便CPU读取,起到加速程序运行的作用);
  3. 从上述内存空间中读取指令并执行;
  4. 程序结束后,根据命令行调用情况(即运行程序的方式)决定是否将PyCodeObject写回硬盘当中(也就是直接复制到.pyc或.pyo文件中);
  5. 之后若再次执行该脚本,则先检查本地是否有上述字节码文件。有则执行,否则重复上述步骤。

  • 若你在命令行直接输入“python path/to/projectDir”(假设projectDir目录含有“__main__.py”文件,以及其他将要调用的模块),那么程序运行结束后便自动为当前目录下所有的脚本生成字节码文件,并保存于本地新文件夹__pycache__当中。(这也有可能是IDE写小项目时自动生成.pyc文件的原因,不过问题描述略微暧昧。详情参见上面知乎问题板块)
或者是,在命令行输入“ python path/to/projectDir/__main__.py”,则生成除 __main__.py外脚本的字节码文件。不过总的来说,上述这两种行为都大大缩短了项目运行前的准备时间(毕竟分工明确的程序,规模应该不会太小,复用率也不会太低。
  • 模块在每次导入前总会检查其字节码文件的修改时间是否与自身的一致。若是则直接从该字节码文件读取内容,否则源模块重新导入,并在最后生成同名文件覆盖当前已有的字节码,从而完成内容的更新(详见import.py)。这样,就避免了修改源代码后与本地字节码文件产生冲突


若想优化生成字节码,应注意这两点:
  • .pyc文件是由.py文件经过编译后生成的字节码文件,其加载速度相对于之前的.py文件有所提高,而且还可以实现源码隐藏,以及一定程度上的反编译。比如,Python3.3编译生成的.pyc文件,Python3.4就别想着去运行啦!→_→
  • .pyo文件也是优化(注意这两个字,便于后续的理解)编译后的程序(相比于.pyc文件更小),也可以提高加载速度。但对于嵌入式系统,它可将所需模块编译成.pyo文件以减少容量

但总的来说,作用上是几乎与原来的.py脚本没有区别的,也就是“然并卵 ”(当然,并非毫无作用。比如,我个人觉得用处最大的地方就是防止别人偷看我的代码。毕竟.py源文件是直接以源码的形式呈现给大家的)


在所有的 Python选项中:
  • -O,表示优化生成.pyo字节码(这里又有“优化”两个字,得注意啦!)
  • -OO,表示进一步移除-O选项生成的字节码文件中的文档字符串(这是在作用效果上解释的,而不是说从-O选项得到的文件去除)
  • -m,表示导入并运行指定的模块
对此,我们可以使用如下格式运行.py文件来生成.pyc文件(以下调用均假设 /path/to目录含有.py脚本):
python -m py_compile /path/to/需要生成.pyc的脚本.py #若批量处理.py文件
                                                  #则替换为/path/to/{需要生成.pyc的脚本1,脚本2,...}.py
                                                  #或者/path/to/
其效果等效于如下代码:
import py_compile
py_compile.compile(r'/path/to/需要生成.pyc的脚本.py') #同样也可以是包含.py文件的目录路径
                                                    #此处尽可能使用raw字符串,从而避免转义的麻烦。比如,这里不加“r”的话,你就得对斜杠进行转义



py_compile是Python的自带模块,这里面就两个函数。其下的 py_compile.compile( file[, cfile[, dfile[, doraise]]])可 将.py文件编译生成.pyc文件(默认),对应的参数解释如下
  1. file,表示需要生成.pyc或.pyo文件的源脚本名(字符串);
  2. cfile,表示需要生成.pyc或.pyo文件的目标脚本名。呃...好像没有区别(>﹏<) ,也就是源脚本-----[巴拉拉赐予你力量!编译!]( * ̄▽ ̄)o ─═≡※:☆----->目标脚本。当然,它默认是以.pyc为扩展名的路径名的字符串(呼...好长)。此外,当且仅当所使用的解释器允许编译成.pyo文件,才能以“.pyo”结尾。这也就是我上面为什么会在函数功能解释上加上“(默认)”这两个字的原因。
  3. dfile,表示编译出错时,将报错信息中的名字“file”替换为“dfile”
  4. doraise设置是否忽略异常。若为True,则抛出PyCompileError异常;否则直接将错误信息写入sys.stderr(温馨提示:sys.stderr是Python自带的标准错误输出
另外,生成.pyo文件的格式调用如下:
python -O -m py_compile /path/to/需要生成.pyo的脚本.py

那么,有人要问了:为什么不是像生成.pyc文件那样采用“python -O /path/to/需要生成.pyo的脚本.py”形式的调用?
“忘记”说明这一点了,很多博客以及书籍都像我上面那样解释“-O”选项的作用,但详细来解释的话是
-O选项,将.pyc文件优化(注意我一直强调的“优化”二字,这里就用到啦!)为.pyo文件,而不是将.py文件优化编译为.pyo文件。(其直接的结果是优化编译后的文件略微小于.pyc文件,也就是“减肥”了。现在,大家知道.pyo文件为什么小的原因了吧!)


注意:
以上无论是生成.pyc还是.pyo文件,都将在当前脚本的目录下生成一个含有字节码的文件夹__pycache__


可能还有人会问,.pyd文件又是什么鬼(>﹏<)?!(问题真多,精分ing...)
别在意,那只是Python的动态链接库。如果要深究,还得扯上C++的知识(长篇大论的,会被喷的啊)。


再啰嗦一句:生成字节码的方法多了去了,不止以上这几种。比如,你们不妨试试将上面命令行调用中的“py_compile”改成“compileall”,而代码行中的“py_compile.compile”改成“compileall.compile_file”或“compileall.compile_dir”,又或者直接使用带有编译功能的IDE生成字节码。


再再啰嗦一句:知道Python运行机制,并不是我们一般人所必须的。但是,了解其加速程序运行以及优化代码的设计思想,对于我们在日后构造缓存系统、如何减少不必要的运行时间,以及同步更新工作内容等问题上起到很大的借鉴作用。

若想要了解更多的内容,可以去翻翻官方文档和其他博客:


 

猜你喜欢

转载自www.cnblogs.com/hude/p/12642597.html
今日推荐