第2章 Python如何运行程序(可选,但推荐看一下)

Python解释器简介

Python是一门编程语言,也是一个名为解释器的软件包,解释器是一种让其他程序运行起来的程序,是代码与机器的计算机硬件之间的软件逻辑层。

Python包安装到机器上,至少包括一个解释器和一套支持库,解释器可能是一个可执行程序,也可能作为链接到另一个程序的一系列库。
根据版本不同,解释器可能是C程序实现的,也可能是Java类实现的。无论如何,Python代码必须在解释器中运行。

程序执行

编写、运行Python脚本的意义,在一是从程序员还是解释器的角度去看

程序员视角

一个Python程序仅是一个包含Python语句的文本文件:

script0.py:

print('hello world')
print(2**100)

这个文件中包含两个Python语句,分别打印出一个字符串和一个表达式的计算结果。

你可以用自己喜欢的编辑器去编写Python程序,通常Python以.py结尾,这种命名在被“导入”时是必需的。

语句出入到文本文件后,由Python去执行,在屏幕上得到打印的结果:

hello world
1267650600228229401496703205376

这里博主我使用的是jupyter进行Python的学习,这段书中使用的是Windows命令行:python *.py

Python视角

当Python运行时,透过表面,还是有一些事情会发生,虽然了解Python内部不是编程所必需的要求,但对Python的运行时结构有一些基本的了解可以帮助你从宏观上把握程序的执行。

字节码编译

执行程序时,Python内部(对大多数用户是完全隐藏的)会先将源代码(文件中的语句)编译成所谓字节码的形式。
字节码是一种低级的、与平台无关的表现形式,Python将每条语句翻译成一组字节码指令,这些字节码可以提高执行速度。

字节码保持为.pyc扩展名的文件,存储在__pycache__的子目录中。
字节码也是Python对于启动速度的一种优化,当第二次运行该程序而程序有没有改动时,会直接运行字节码。

源文件的改变:Python检查源文件和字节码文件的时间戳,确认他是否必须重新编译。
Python版本:导入机制同时检查是否需要因为使用了不同的Python版本而重新编译,这些版本信息在3.2之前存储在字节码文件中,在3.2及之后,存储在字节码文件名中

当以上两者任一改变时,需要重新编译源文件为字节码。
当源文件消失后,有字节码文件的话,也可以执行程序。
当字节码文件无法生成在机器上,字节码会生成在内存中。因为字节码可以提升启动速度,所以还是能够创建字节码文件的好。

字节码只会针对那些被导入(import)的文件而生成,而不是顶层的执行脚本。大概是指当逻辑走到这一步时,遇到了import才会导入/生成字节码,即边解释边运行。

Python虚拟机(PVM)

被编译或导入的字节码,之后被发送到通常称为Python虚拟机(Python Virtual Machine,PVM)的程序上来执行。PVM就是迭代运行字节码指令的一个大循环,是Python的运行时引擎,无须独立安装,是Python的一部分,是实际运行脚本的组件,是Python解释器的最后一步。

字节码、PVM等,都是对程序员所隐藏的,程序员无须考虑这些结构,Python负责所有运行这些文件的逻辑,交给Python就是了。

性能的含义

与C、C++相比,Python没有make的编译过程,是即写即能运行的,字节码也不是机器的二进制码,是Python的一种特定的表现形式。

=> 源文件(.py) >>> 字节码(.pyc) >>> 运行时(PVM)

即便是导入字节码运行,字节码到机器码仍有很多的步骤,这使得Python不会像C那样快,而因为字节码的存在,而不需每次都要解释源文件,这使得比传统的解释语言要快。

开发的含义

因为Python的边解释边运行的动态解释运行特性,以及可移植性,无论是嵌入到其他系统,还是修改、增加功能,都相对比编译语言方便了很多。
编译语言所做的,是在编译时已经完成了的,这使得运行时只是执行那时的结果。而Python,他的结果是运行时产生的。
这使得Python带来程序员更加动态的编程体验。

执行模型的变体

以上讲述的是Python的标准实现形式,但随着发展,也出现了多种变体

Python的各种实现

作者在编写此版书时,Python语言有5种主要的实现方式:CPython、Jython、IronPython、Stackless和PyPy。

尽管Python的实现方式之间充满了思想和工作的杂交,但是他们每一个都是独立安装的软件系统,拥有各自的开发者和用户群体。

另外还有Cython和Shed Skin系统,但他们将在之后作为优化工具讨论,因为他们没有实现标准的Python语言。前者是Python和C的混合体,后者是隐式的静态类型化。

PyPy是现成的Cpython替代品,因为它可以更快的运行大多数程序。
Jython和IronPython是完全独立的Python实现,用来为不同的运行时环境架构编译Python源代码,从而能够提供直接的Java和.NET组件的使用接口。

CPython:标准Python

最初的、标准的Python实现方式通常称作CPython(或者也可以直接称为Python)。这个名字来自它是由可移植的ANSI C语言代码编写而成的这个事实。网上下载的、机器上预装的、没有特殊场合需求的,都是CPython。

除非你想要使用Python脚本化的Java和.NET应用,或是利用Stackless和PyPy的编译优势。否则通常只需要使用标准的CPython系统。因为CPython是这门语言的参照实现,所以与其他的替代系统相比,他运行速度最快、最完整、最新、最健全。

Jython:基于Java的Python

Jython(最初称为JPython)是一种Python语言的可选实现方式,其目的是与Java编程语言继承。
Jython包含Java类,这些类将Python源代码编译成Java字节码,并将得到的字节码定向到Java虚拟机(JVM)上。

Jython的目的是让Python代码能够脚本化Java应用程序,就好像CPython运行Python脚本化C和C++组件一样。他实现了与Java的无缝集成,由于Python代码被翻译成Java字节码,因此在运行时看起来就像真正的Java程序一样。Jython脚本可以应用于开发Web applet和servlet,构建基于Java的GUI等。
此外Jython包括对继承的支持,运行Python代码导入并使用Java类(把这些类当作由Python编写的一样),以及让Java代码能够把Python代码当作内嵌的语言来运行。虽然相较于CPython,Jython既不快也不够健壮,但是他解决了希望寻找一门作为前端脚本语言的Java开发者的诉求。

IronPython:基于.NET的Python

IronPython的设计目的是让Python程序可以与Windows上的.NET以及与之对应的Linux上开源的Mono编写的应用相集成。
.NET和C#和微软早期COM模型一样的精神,旨在称为语言无关的对象通信层。IronPython运行Python程序同时扮演客户端和服务器端的角色,与.NET语言进行来回访问,以及在Python代码中利用诸如Sliverlight框架的.NET技术。
IronPython和Jython是同一个创始人开发的,最初由微软公司开发,现在成了一个开源项目,同时为了得到更优异的性能而利用一些重要的优化工具。

Stackless:注重并发的Python

Stackless具有更专注的目标,是标准CPython针对并发性而优化的一个增强版实现,他不会再C语言调用栈上保存状态,这使得Python更容易移植到较小的栈架构中,提供了更高效的多处理选项,并促进像协议(coroutine)这样新颖的编程结构的出现,在Stackless为Python带来的所有优化中,微线程是Python原生多线程工具的一个更高效和更轻量的替代品。带来了更好的程序结构、更可读的代码以及更强的开发效率。

EVE Online的创建者CCP游戏公司就是Stackless Python的忠实用户,也是Python最成功的业界案例之一。

PyPy:注重速度的Python

PyPy系统是CPython标准的另一个实现,他更注重性能。它提供了一个带有即时编译器(just-in-time,JIT)的Python快速实现,能够在安全环境中运行不信任代码的“沙箱”模型的工具,默认对前一节中Stackless Python系统的支持以及对大型并行需求的微线程支持。

PyPy是原先的Psyco即时编译器的继承者,并将Psyco纳入一个追求速度的纯Python实现。即时编译器事实上只是PVM的一个扩展,他将字节码中的部分直接转换成运行速度更快的二进制机器码,这一切发生在你的程序运行的时候,而非运行前的编译阶段,而且即时编译器能够通过追踪程序中的对象数据类型,创建针对Python语言动态特性的机器代码,通过这种方式部分的替换字节码,程序将在运行的时候越跑越快。此外,一些Python代码在PyPy下运行也会占用更少的内存。

PyPy如今号称在一系列评测代码上比CPython提速5.7倍,在某些情况下,他利用动态优化机会的优势,让Python代码运行的跟C代码一样快,甚至有时超越C。这点对算法密集和计算密集型的程序尤为明显。这种提速对许多领域来说都是一种令人不可抗拒的优势,甚至从某种意义上比Python前卫的语言特性更受人追捧。同样重要的是,PyPy也对内存空间进行了优化,一次发布测评中,PyPy使用247MB的内存花10.3秒完成了编译,大大超过了CPython的648MB和89秒。

PyPy的编译链工具也通常足够支持包括Pyrolog(一个使用Python编写,利用PyPy翻译的Prolog解释器)在内的其他语言。

许多人今天仍将PyPy视作Python的一条重要发展方向,如果你要编写CPU密集型的代码,那么PyPy值得你拥有。

执行优化工具

前面的大部分变体仍以相似的方式实现Python语言,其它系统包括Cython混合语言,Shed Skin C++转换器,以及PyPy与Psyco中的即时编译器,则试着优化了基本执行模块,这些系统并不是你现阶段学习Python的必备知识,但是简要地了解他们在执行模型中所处的位置能够帮助你揭开执行模型的神秘面纱。

Cython:Python和C的混合体

Cython系统(基于Pyrex项目所完成的工作)是一种混合的语言,他为Python代码配备了调用C函数以及使用变量、参数和类属性的C类型声明的能力。Cython代码可以编译成使用Python/C API的C代码,随后可以再完整的编译,尽管与标准Python并不完全兼容,Cython对于包装外部的C库以及提高Python的C扩展的编码效率都很有用。

Shed Skin:Python到C++的转换器

他尝试将Python代码翻译成C++代码,然后使用机器中的C++编译器将得到的C++代码编译为机器码。
他给Python程序施加了一种隐式的静态类型约束,在Python中显得格格不入,不过它具有超越标准Python代码以及类Psyco扩展的潜质。

Psyco:原先的即时编译器

Psyco系统并不是Python的另一种实现方式,而是一个可以让程序运行的更快的扩展字节码执行模块组件。
他已经过时,但仍可被下载,他的思想被融入到更加完整的PyPy系统中。

当程序运行时,Psyco会收集正在传递过程中的对象的类别信息,这些信息可以用来裁剪对象的类型,从而生成高效的机器代码,机器代码一旦形成,就会替代对应的原始字节码,从而加快程序的整体执行速度,实际的效果就是通过使用Psyco,使程序在整个运行过程中执行得越来越快。

因为字节码的转换在程序运行时发生,所以Pysco往往被看作一个即时编译器(JIT),不过与Java语言的JIT不同,Psyco是一个专有的JIT编译器:他生成机器代码将数据类型精简至程序实际所使用的类型,如果程序一部分在不同的时候采用了不同的数据类型,Psyco可以生成不同版本的机器码以支持每一个不同的类型组合。

冻结二进制文件

有时候人们只需要一个独立的二进制可执行代码,这是一个比执行流程概念更接近于打包发布概念的东西。

一些第三方工具,将Python程序转为可执行程序,他们在Python世界中被称作冻结二进制文件(frozen binary)。这些程序可以不安装Python环境而独立运行。

冻结二进制文件能够将程序文件的字节码、PVM(解释器)以及任何程序所需要的Python支持文件捆绑在一起形成一个单独的文件包。这个过程存在着一些不同的变体,但最终的结果是一个单独的可执行二进制程序,这个程序可以很容易地向客户发布。

py2exe(支持Windows),PyInstaller(支持Linux、Mac OS X),py2app(Mac OS X),freeze和cx_freeze(跨平台)

例如py2exe可以封装那些使用了tkinter、PMW、wxPython、PyGTK GUI的独立程序;应用pygame进行游戏编程的程序;win32com客户端的程序等。

冻结二进制文件与真实的编译器输出结果有所不同:他们是字节码与虚拟机的打包,除了必要的初始,其他与源代码程序运行速度完全相同。他并不小,毕竟包含一个PVM,也并不特别大。因为内嵌了PVM,所以用户机器也并不需要安装Python。而且由于代码内嵌到都冻结的二进制码中,所以对于接收者来说,代码都是隐藏起来的。

对于商业软件的开发者来说,单文件封装的构架特别有吸引力,例如一个基于tkinter工具包的用户界面可以封装成一个可执行文件,而且可以在光盘中或网络上为独立程序进行发售。

未来的可能性

这里描述的运行时执行模型是Python实现的一种产物,并不是语言本身,或许在本书销售过程中出现一种完全将Python源代码变成机器代码的传统模式的编译器,但是这20年都没出现过,所以好像不可能。话说这本书是国人翻译出版的第五版,而第六版的原版已经发售了。

仍有更多的变体在研发中:
Parrot项目的目标是将包括Python在内的多种编译语言通用的字节码格式、虚拟机以及优化技术。貌似是一个实现方式可以把多种语言实现出来,但是当时实际运行速度还不如Python自身的PVM,在蛋糕挑战中落败(这个段子在中文搜不到,外网很难登录,在水木清华上有个原文的转载)。
翻译:
巨蟒对战鹦鹉的挑战赛
因此,2004年最激动人心的事件之一可能就是
巨蟒对战鹦鹉挑战赛。简而言之,表现
在CPython和Parrot虚拟机中执行Python字节码将会
在2004年O’Reilly开源大会上被测量,并代表
输的一方,要么是圭多·范·罗森姆,要么是丹·苏格斯基,被人扔到脸上。另外,输的人要付10美元和一杯酒
的赢家。更重要的是,这当然是关乎自尊的问题。
Python versus Parrot challenge, the Pie-thon

Unladen Swallow是由一个谷歌工程师开发的开源项目,试图将Python提速5倍,从而在许多应用场景替代C,曾是CPython的一个优化分支,目的在于通过添加一个即时编译器来提到Python的兼容性和速度,但是失败告终。然而他的教训开启了开发者的另一个思路,并且获得重大进展,现在已经有相当多的和编译器相关的项目正在进行中,Numba(数值)、Pyjion(微软)、Pyston(LLVM)、Transcrypt(从Python到JavaScript)、Pythran(从Python到C++)等

尽管未来的实现方案有可能从某种程度上改变Python的运行时结构(runtime structure),但就接下来的一个时期内来看,字节码编译仍然将会使一种标准。字节码的可移植性和运行时的灵活性对于很多Python系统来说是很重要的特性。此外为了实现静态编译,而增加类型约束声明将会破坏这种灵活、紧凑、简明以及所有代表了Python编码精神的特性,由于Python本身高度动态的本质,今后任何实现方式都可能保留诸多当前的PVM的产物。

本章小结

本章介绍了Python的执行模型(Python是如何运行程序的),并探索了这个模型的一些变体(即时编译器以及类似的工具)。
Python解释器试运行你所编写的Python程序的程序
源代码是为程序所写的语句:它由文本文件中的文本组成,通常以.py作为后缀名
字节码是Python将程序编译后所得到的低级形式。Python自动将字节码保存到后缀名为.pyc的文件中
PVM是Python虚拟机,他是Python的运行时引擎,能解释编译得到的字节码
Psyco、Shed Skin以及冻结二进制文件都是执行模型的变体。除此之外,在接下来的两个问题中得到的其他Python实现,以代替字节码和虚拟机或是添加工具和JIT的形式对Python的执行模型进行改进
CPython是Python语言的标准实现,Jython和IronPython分别是Python程序在Java和.NET环境中的实现,他们都是Python的替代编译器
Stackless是一种针对并发性而增强的Python版本,而PyPy是针对速度而增强的Python实现。PyPy作为Psyco的继任者,融合了Psyco中的JIT概念。

本章虽然算得上是选读,但是内容着实有趣,而且开阔眼界!

上一篇:在tinycolinux上编译pypy和hippyvm


下一篇:Python必学之编译器用哪个好?你用错了吧!