本文概述
Python很棒。
令人惊讶的是, 这是一个相当含糊的陈述。 ” Python”是什么意思?我是说Python是抽象接口吗?我的意思是CPython, 它是通用的Python实现(不要与类似名称的Cython混淆)吗?还是我完全是其他意思?也许我是指Jython, IronPython或PyPy。也许我真的已经走出了深渊, 而我正在谈论RPython或RubyPython(这是非常不同的东西)。
虽然上面提到的技术是通用名称和通用引用的, 但是其中一些功能完全不同的目的(或者至少以完全不同的方式运行)。
在使用Python界面的整个过程中, 我经常使用大量的。* ython工具。但是直到最近, 我才花时间了解它们的含义, 它们的工作原理以及为什么需要它们(以自己的方式)。
在本教程中, 我将从头开始, 逐步介绍各种Python实现, 最后对PyPy进行全面介绍, 我相信这是该语言的未来。
一切始于对” Python”实际上是什么的理解。
如果你对机器代码, 虚拟机等有很好的了解, 请随时跳过。
” Python是解释还是编译的?”
这是Python初学者的普遍困惑。
进行比较时首先要意识到的是” Python”是一个接口。有关于Python应该做什么以及应该如何工作的规范(与任何接口一样)。并且有多种实现方式(与任何接口一样)。
要意识到的第二件事是, “解释”和”编译”是实现的属性, 而不是接口。
因此, 问题本身的格式并不正确。
是Python解释还是编译?这个问题的格式不是真的。
就是说, 对于最常见的Python实现(CPython:用C编写, 通常简称为” Python”, 如果你不知道我在说什么, 那么肯定会使用什么), 答案是:解释, 并进行了一些编译。 CPython将Python源代码编译为字节码, 然后解释该字节码, 并立即执行。
*注意:这不是传统意义上的”编译”。通常, 我们会说”编译”是一种高级语言, 并将其转换为机器代码。但这是一种”编译”。
让我们更仔细地看一下这个答案, 因为它将帮助我们理解本文后面的一些概念。
字节码与机器码
了解字节码与机器码(又称本机码)之间的区别非常重要, 也许可以通过示例来最好地说明一下:
- C编译为机器代码, 然后直接在你的处理器上运行。每条指令都指示你的CPU移动内容。
- Java编译为字节码, 然后在Java虚拟机(JVM)上运行, 这是执行程序的计算机的抽象。然后, 每条指令都由与计算机交互的JVM处理。
简而言之:机器代码要快得多, 但字节码则更可移植且更安全。
机器代码根据你的机器而有所不同, 但字节码在所有机器上看起来都相同。可能有人会说, 机器代码已针对你的设置进行了优化。
返回到CPython实现, 工具链过程如下:
- CPython将你的Python源代码编译为字节码。
- 然后, 该字节码在CPython虚拟机上执行。
初学者经常认为Python是因为.pyc文件而编译的。有一个事实:.pyc文件是编译后的字节码, 然后将其解释。因此, 如果你之前已经运行过Python代码并拥有.pyc文件, 那么它第二次运行会更快, 因为它不必重新编译字节码。
备用VM:Jython, IronPython等
如前所述, Python有几种实现。再次, 如前所述, 最常见的是CPython, 但是出于比较指南的考虑, 还有其他一些应该提及。这是一个用C语言编写的Python实现, 被视为”默认”实现。
但是其他Python实现又如何呢? Jython是最著名的应用程序之一, 它是使用JVM编写的用Java编写的Python实现。虽然CPython生成可在CPython VM上运行的字节码, 但Jython生成可在JVM上运行的Java字节码(这与编译Java程序时生成的东西相同)。
你可能会问:”为什么要使用替代实现?”。好吧, 其中一个是, 这些不同的Python实现可与不同的技术堆栈很好地配合使用。
CPython使得为Python代码编写C扩展变得非常容易, 因为最后它是由C解释器执行的。另一方面, Jython使使用其他Java程序变得非常容易:你可以毫不费力地导入任何Java类, 从Jython程序中调用并利用Java类。 (此外:如果你还没有仔细考虑过, 这实际上是很疯狂的。我们现在可以混合和混合不同的语言, 并将它们编译成相同的内容。(如Rostin所述, 混合使用Fortran和C代码已经有一段时间了, 因此, 这当然不一定是新的。但这仍然很酷。
例如, 这是有效的Jython代码:
[Java HotSpot(TM) 64-Bit Server VM (Apple Inc.)] on java1.6.0_51
>>> from java.util import HashSet
>>> s = HashSet(5)
>>> s.add("Foo")
>>> s.add("Bar")
>>> s
[Foo, Bar]
IronPython是另一种流行的Python实现, 完全用C#编写并且针对.NET堆栈。特别是, 它可以在你可以称为.NET虚拟机(Microsoft的通用语言运行时, CLR)上运行, 与JVM相当。
你可能会说Jython:Java :: IronPython:C#。它们在相同的VM上运行, 你可以从IronPython代码导入C#类, 从Jython代码导入Java类, 等等。
无需接触非CPython Python实现, 就可以生存。但是切换有很多好处, 其中大部分取决于你的技术堆栈。使用很多基于JVM的语言? Jython可能适合你。关于.NET堆栈?也许你应该尝试IronPython(也许你已经拥有了)。
顺便说一句:虽然这不是使用其他实现的理由, 但请注意, 这些实现实际上在行为上有所不同, 除了对待Python源代码的方式不同。但是, 这些差异通常很小, 随着这些实现的积极开发, 它们会随着时间的流逝而消失或消失。例如, IronPython默认使用Unicode字符串。但是, 对于版本2.x, CPython默认为ASCII(对于非ASCII字符, 默认为UnicodeEncodeError), 但是对于3.x, 默认情况下确实支持Unicode字符串。
即时编译:PyPy和未来
因此, 我们有一个用C编写的Python实现, 一个用Java编写, 一个用C#编写。接下来的逻辑步骤:用Python编写的Python实现。 (受过良好教育的读者会注意到这有点误导。)
这可能会使你感到困惑。首先, 让我们讨论即时(JIT)编译。
JIT:为什么和如何
回想一下, 本机代码比字节码快得多。好吧, 如果我们可以编译一些字节码然后作为本机代码运行呢?我们必须付出一些代价才能编译字节码(即时间), 但是如果最终结果更快, 那就太好了!这就是JIT编译的动机, 它是一种混合技术, 将解释器和编译器的优点融合在一起。从根本上讲, JIT希望利用编译来加快解释系统的速度。
例如, JIT采取的常见方法是:
- 标识经常执行的字节码。
- 将其编译为本机代码。
- 缓存结果。
- 无论何时设置要运行相同的字节码, 都应获取预先编译的机器代码并获得好处(即提高速度)。
这就是PyPy实现的全部内容:将JIT引入Python(有关以前的工作, 请参阅附录)。当然, 还有其他目标:PyPy的目标是成为跨平台的, 轻巧的存储和无栈支持。但是JIT确实是它的卖点。作为一系列时间测试的平均值, 据说可以将性能提高6.27倍。有关详细信息, 请参阅PyPy Speed Center的以下图表:
PyPy很难理解
PyPy具有巨大的潜力, 并且目前与CPython高度兼容(因此它可以运行Flask, Django等)。
但是关于PyPy却有很多困惑(例如, 请参阅有关创建PyPyPy的荒谬建议……)。我认为这主要是因为PyPy实际上是两件事:
用RPython编写的Python解释器(不是Python(我之前说谎))。 RPython是具有静态类型的Python的子集。在Python中, 对类型进行严格推理是”几乎不可能的”(为什么这么难?请考虑以下事实:
x = random.choice([1, "foo"])
将是有效的Python代码(贷给Ademan)。 x是什么类型?如果不严格执行变量类型, 我们又该如何推断呢?)使用RPython, 你牺牲了一些灵活性, 但是却使推理内存管理和其他事情变得非常容易, 这可以进行优化。
编译器可为各种目标编译RPython代码并添加JIT。默认平台是C, 即RPython-to-C编译器, 但你也可以将JVM和其他平台作为目标。
仅在本Python比较指南中为清楚起见, 我将它们称为PyPy(1)和PyPy(2)。
你为什么需要这两件事, 为什么要在同一屋檐下?这样想:PyPy(1)是用RPython编写的解释器。因此, 它接受用户的Python代码并将其编译为字节码。但是解释器本身(用RPython编写)必须由另一个Python实现来解释才能运行, 对吗?
好吧, 我们可以只使用CPython运行解释器。但这不会很快。
相反, 我们的想法是, 我们使用PyPy(2)(称为RPython工具链)将PyPy的解释器编译为用于在我们的计算机上运行的另一个平台(例如C, JVM或CLI)的代码, 并在JIT中添加好。神奇:PyPy将JIT动态添加到解释器中, 从而生成自己的编译器! (同样, 这很疯狂:我们正在编译解释器, 并添加了另一个单独的独立编译器。)
最后, 结果是一个独立的可执行文件, 可解释Python源代码并利用JIT优化。这正是我们想要的!一口气, 但也许这张图会有所帮助:
重申一下, PyPy的真正优点在于, 我们可以在RPython中为自己编写许多不同的Python解释器, 而不必担心JIT。然后, PyPy将使用RPython工具链/ PyPy(2)为我们实现JIT。
实际上, 如果我们变得更加抽象, 则理论上你可以为任何一种语言编写解释器, 将其提供给PyPy, 并获得该语言的JIT。这是因为PyPy专注于优化实际的解释器, 而不是其解释语言的细节。
从理论上讲, 你可以为任何一种语言编写解释器, 将其提供给PyPy, 然后获得该语言的JIT。
作为简短的题外话, 我想提到JIT本身绝对令人着迷。它使用一种称为跟踪的技术, 该技术执行如下:
- 运行解释器并解释所有内容(不添加JIT)。
- 对解释的代码进行一些简要的分析。
- 确定你之前执行的操作。
- 将这些代码位编译为机器代码。
欲了解更多, 这篇论文是高度可访问的并且非常有趣。
总结一下:我们使用PyPy的RPython-to-C(或其他目标平台)编译器来编译PyPy的RPython实现的解释器。
本文总结
在对Python实现进行长时间的比较之后, 我不得不问自己:为什么这么好?为什么这个疯狂的想法值得追求?我认为Alex Gaynor在他的博客上说得很好:” [PyPy是未来”, 因为[它]提供了更快的速度, 更多的灵活性, 并且是Python成长的更好的平台。”
简而言之:
- 快速, 因为它可以将源代码编译为本地代码(使用JIT)。
- 它非常灵活, 因为它只需很少的额外工作即可将JIT添加到你的口译员。
- 灵活(再次)是因为你可以用RPython编写解释器, 比C语言更容易扩展(事实上, 它很容易, 有一个编写你自己的解释器的教程)。
附录:你可能听说过的其他Python名称
-
Python 3000(Py3k):Python 3.0的替代命名, 它是一个主要的, 向后不兼容的Python发行版, 于2008年问世。Py3k团队预测, 要完全采用此新版本大约需要五年的时间。尽管大多数(警告:轶事声称)Python开发人员继续使用Python 2.x, 但人们越来越意识到Py3k。
- Cython:Python的超集, 包含用于调用C函数的绑定。
- 目标:允许你为Python代码编写C扩展。
- 还可以让你向现有的Python代码添加静态类型, 从而可以对其进行编译并达到类似C的性能。
- 这类似于PyPy, 但不相同。在这种情况下, 你必须在将用户代码传递给编译器之前强制输入。使用PyPy, 你可以编写普通的旧Python, 并且编译器可以处理所有优化。
-
Numba:”即时专业编译器”, 它将JIT添加到带注释的Python代码中。用最基本的术语来说, 你可以给它一些提示, 并且可以加快代码的速度。 Numba是Anaconda发行版的一部分, Anaconda发行版是一组用于数据分析和管理的软件包。
-
IPython:与讨论的其他内容完全不同。 Python的计算环境。与GUI工具包和浏览器体验等交互可交互
- Psyco:Python扩展模块, 是Python JIT的早期尝试之一。但是, 此后被标记为”未维护且已死亡”。实际上, Psyco的首席开发人员Armin Rigo现在正在研究PyPy。
Python语言绑定
-
RubyPython:Ruby和Python VM之间的桥梁。允许你将Python代码嵌入到Ruby代码中。你定义Python的开始和停止位置, 然后RubyPython在虚拟机之间封送数据。
-
PyObjc:Python和Objective-C之间的语言绑定, 充当它们之间的桥梁。实际上, 这意味着你可以利用Python代码中的Objective-C库(包括创建OS X应用程序所需的一切)以及Objective-C代码中的Python模块。在这种情况下, 使用C(这是Objective-C的子集)编写CPython很方便。
-
PyQt:PyObjc为你提供OS X GUI组件的绑定, 而PyQt对Qt应用程序框架也具有相同的功能, 使你可以创建丰富的图形界面, 访问SQL数据库等。另一个旨在将Python的简单性引入其他框架的工具。
JavaScript框架
-
pyjs(Pyjamas):用于在Python中创建Web和桌面应用程序的框架。包括Python到JavaScript的编译器, 小部件集和其他一些工具。
-
Brython:用JavaScript编写的Python VM, 允许在浏览器中执行Py3k代码。
评论前必须登录!
注册