PyPy 是自我翻译吗?
我理解得对吗?PyPy解释器实际上是自己解释自己,然后再翻译自己吗?
这是我目前的理解:
- RPython的工具链会部分执行要翻译的程序,以获得一种预处理的版本,然后进行注释和翻译。
- PyPy解释器在CPython上运行,部分解释自己,然后把控制权交给它的RPython部分,进行翻译?
如果这是真的,那真是我见过的最让人费解的事情之一。
2 个回答
免责声明:我不是PyPy方面的专家,特别是对RPython翻译的细节不太了解,我只是引用我之前读过的内容。如果想了解RPython翻译是如何工作的,可以看看这个回答。
答案是,可以的(但前提是它必须先用CPython编译过)。
详细说明:
一开始这听起来很复杂,甚至有点矛盾,但一旦你理解了,就简单多了。可以查看一下维基百科上的答案。
程序开发中的自举(Bootstrapping)始于1950年代,那时每个程序都是用十进制或二进制代码逐步在纸上构建的(就是1和0),因为当时没有高级编程语言,没有编译器,没有汇编器,也没有链接器。为了新电脑(比如IBM 650),手动编写了一个小型汇编程序,它将一些指令转换成二进制或十进制代码:A1。这个简单的汇编程序随后用刚定义的汇编语言重写,但增加了一些扩展,以便使用更多复杂操作码的助记符。
这个过程叫做软件自举。基本上,你先用一种已经存在的低级语言(比如汇编语言)来构建一个工具,比如C++编译器。现在你有了C++,就可以用C++编写一个C++编译器,然后用之前的汇编C++编译器来编译这个新编译器。等你把新编译器编译好后,就可以用它来编译自己。
所以,简单来说,就是手动编写第一个计算机工具,使用这个解释器制作一个稍微好一点的工具,再用那个工具制作更好的工具……最终你就得到了今天所有复杂的软件!:)
另一个有趣的例子是CoffeeScript语言,它是用……CoffeeScript编写的。(不过这个用例仍然需要使用外部解释器,也就是Node.js)
PyPy解释器在CPython之上运行,部分自我解释,然后将控制权交给它的RPython部分,进行翻译?
你可以用已经编译好的PyPy解释器来编译PyPy,或者也可以用CPython来编译。不过,由于PyPy现在有了即时编译(JIT),用它自己来编译会比用CPython快。(在大多数情况下,PyPy现在比CPython快)
PyPy的翻译过程其实没有听起来那么复杂。
简单来说,它就是一个处理Python函数、类和其他对象的Python程序(不是处理Python源代码),然后输出C代码。不过,它并不是处理任何Python对象,而是只能处理特定的形式,这些形式是你用RPython写的代码所能得到的。
因为这个翻译工具链是一个Python程序,所以你可以在任何Python解释器上运行它,当然也包括PyPy的Python解释器。所以这并没有什么特别之处。
由于它翻译的是RPython对象,你可以用它来翻译PyPy的Python解释器,而这个解释器是用RPython写的。
但是你不能在翻译框架本身上运行它,因为翻译框架不是RPython。只有PyPy的Python解释器本身是RPython。
事情变得有趣是因为RPython代码也是Python代码(但反过来不成立),而且RPython并不“真正存在”于源文件中,而只存在于一个正在运行的Python进程的内存里,这个进程必然包含其他非RPython的代码(例如,没有“纯RPython”的导入或函数定义,因为翻译器处理的是已经定义和导入的函数)。
记住,翻译工具链处理的是内存中的Python代码对象。Python的执行模型意味着这些在某些Python代码运行之前是不存在的。你可以想象,启动翻译过程的样子大致是这样的,如果你简化一下:
from my_interpreter import main
from pypy import translate
translate(main)
众所周知,仅仅导入main
就会运行很多Python代码,包括所有其他my_interpreter
导入的模块。但翻译过程开始分析的是函数对象 main
;它从来没有看到,也不关心,为了得到main
而执行的任何代码。
可以这样理解,“用RPython编程”意味着“写一个Python程序,这个程序生成一个RPython程序,然后把它交给翻译过程”。这相对容易理解,也有点像许多其他编译器的工作方式(例如,可以把用C编程理解为你在写一个C预处理程序,这个程序生成一个C程序,然后交给C编译器)。
在PyPy的情况下,事情变得复杂是因为这三个组件(生成RPython程序的Python程序、RPython程序和翻译过程)都加载在同一个Python解释器中。这意味着可能会有一些函数在用某些参数调用时是RPython,而用其他参数调用时则不是,或者在生成你的RPython程序时调用翻译框架的辅助函数,还有很多其他奇怪的事情。所以情况变得有些模糊,你不能简单地把你的源代码行分成“待翻译的RPython”、“生成我的RPython程序的Python”和“把RPython程序交给翻译框架”。
PyPy解释器在CPython上运行,部分执行自身的解释。
我想你在这里提到的是PyPy在翻译过程中使用的流对象空间来进行抽象解释。即使这样也没有看起来那么疯狂和难以理解。我对PyPy的这一部分了解得不多,但据我理解:
PyPy通过将所有Python解释器的操作委托给一个“对象空间”来实现所有操作,这个对象空间包含所有基本内置操作的实现。但你可以插入不同的对象空间来获得不同的效果,只要它们实现相同的“对象空间”接口,解释器仍然能够“执行”Python代码。
PyPy翻译工具链处理的RPython代码对象是可以被解释器执行的Python代码。因此,PyPy在翻译工具链中重用了它的Python解释器的一部分,通过插入流对象空间。当用这个对象空间“执行”代码时,解释器实际上并没有执行代码的操作,而是生成流图,这类似于许多其他编译器使用的中间表示;这只是一个简单的、可以被机器操作的代码表示,供进一步处理。这就是普通(R)Python代码对象如何转变为翻译过程其余部分的输入。
由于通常翻译的内容是PyPy的Python解释器,它确实“解释自身”使用流对象空间。但这实际上意味着你有一个Python程序在处理Python函数,包括那些正在进行处理的函数。就其本身而言,这并没有比给自己应用装饰器或让一个包装类包装自己的实例(或包装类本身)更难以理解。
嗯,这有点啰嗦。希望这对你有帮助,也希望我没有说错什么;如果有,请纠正我。