PyPy是否可以翻译自身?

50
我理解的是:PyPy 解释器会执行自身,然后再进行翻译。具体来说:
- RPython 工具链会部分执行要翻译的程序,以获取某种预处理版本以进行注释和翻译。 - PyPy 解释器在 CPython 上运行,它会执行自身的部分解释,然后将控制权交给它的 RPython 这一部分,完成翻译工作。
如果这是真的,那么这是我见过的最让人费解的事情之一。

11
如果你觉得这很让人费解,那么你应该看看Lisp。 - Matt Fenwick
等等,这是真的吗?RPython不是一个独立的软件,而只是PyPy的一部分?不可能。 - alexgolec
2
你所谓的“RPython”是什么东西,你期望它是一款软件?RPython是一种编程语言,不多不少。PyPy代码库包括这种语言的编译器,尽管它与完整的Python解释器大部分是分离的(它没有包含在解释器中,而是重用Python解释器进行某些分析的早期部分)。 - user395760
那么RPython编译器重用PyPy代码库的部分来对从dis接收到的字节码进行抽象解释? - alexgolec
2个回答

66

PyPy的翻译过程实际上比听起来的概念递归要简单得多。

它实际上只是一个处理Python函数/类/其他对象(不是Python源代码)并输出C代码的Python程序。但当然它不会处理所有的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 导入的所有其他模块。但翻译过程始于分析 function object main; 它从不关心产生 main 的任何代码,也不在意执行了什么。
一种思考方式是,“使用RPython编程”意味着“编写一个Python程序来生成RPython程序,然后将其提供给翻译过程”。这种方式相对容易理解,并且与许多其他编译器的工作方式有些相似(例如,程序员可以将C编程视为编写一个C预处理器程序,该程序生成一个C程序,然后将其提供给C编译器)。
在PyPy的情况下,事情变得混乱起来,因为所有3个组件(生成RPython程序的Python程序、RPython程序和翻译过程)都加载到同一个Python解释器中。这意味着很可能有一些函数在某些参数下是RPython,而在另一些参数下则不是;还有可能调用翻译框架的辅助函数来生成你的RPython程序等等,所以在边缘处的情况变得非常模糊,你不能把源代码清晰地分成“要翻译的RPython代码”、“生成我的RPython程序的Python代码”和“将RPython程序交给翻译框架”的部分。

运行在CPython上的PyPy解释器,执行部分自我解释

我认为你这里暗示的是PyPy在翻译过程中使用流对象空间进行抽象解释。即使这看起来有些疯狂和令人困惑,但实际上并不像初看起来那么神秘。我对PyPy的这部分了解较少,但据我所知:
PyPy通过将所有Python解释器的操作委托给“对象空间”,来实现Python解释器的所有操作。该空间包含所有基本内置操作的实现。但是可以插入不同的对象空间以获得不同的效果,只要它们实现了相同的“对象空间”接口,解释器仍然能够“执行”Python代码。
PyPy翻译工具链处理的RPython代码对象是可以由解释器执行的Python代码。因此,PyPy将其Python解释器的一部分作为翻译工具链的一部分进行重用,通过插入流对象空间。当使用此对象空间“执行”代码时,解释器实际上并不执行代码的操作,而是生成类似于许多其他编译器使用的中间表示形式的流图。这只是一种简单的机器可操纵的代码表示形式,用于进一步处理。这就是如何将常规的(R)Python代码对象转换为翻译过程的其余部分的输入。
由于使用翻译过程进行翻译的通常是PyPy的Python解释器,因此它确实使用流对象空间“自我解释”。但是,这仅仅意味着你有一个处理Python函数的Python程序,包括执行处理的函数。本质上,这并没有比对自身应用装饰器或者使用包装器类封装其自身(或封装类本身)更

1
前两句话让我的脑袋自动关闭了。 - nerdoc

14
免责声明:我不是PyPy的专家,特别是我不了解RPython翻译的细节,我只引用了我以前读过的东西。有关RPython翻译如何工作的更具体帖子,请查看此答案
答案是可以的(但只有在使用CPython编译后)。
更长的描述:
一开始似乎非常费解和自相矛盾,但一旦你理解了它,就很容易了。请查看维基百科上的答案。
程序开发中的引导程序始于20世纪50年代,当时每个程序都是用十进制代码或二进制代码逐位手工编写在纸上的,因为没有高级计算机语言、没有编译器、没有汇编器和没有链接器。手工编写了一个小型汇编程序,用于新计算机(例如IBM 650),它将几条指令转换为二进制或十进制代码:A1。然后,这个简单的汇编程序用刚定义的汇编语言进行了重写,但加入了扩展,以便使用一些附加助记符来处理更复杂的操作码。
这个过程被称为软件引导。基本上,您需要在已经存在的低级语言(一切都必须从二进制代码开始编码)中构建一个工具,比如说一个C++编译器。现在您已经有了C++,您可以使用C++编写C++编译器,然后使用ASM C++编译器来编译您的新编译器。一旦您有了新编译器的编译版本,您就可以使用它来编译自身。
所以基本上,通过手动编码制作第一个计算机工具,使用该解释器制作另一个稍微更好的工具,然后使用那个工具制作更好的工具,...最终您就可以得到今天所有复杂的软件! :)
另一个有趣的案例是CoffeeScript语言,它是用CoffeeScript编写的。(尽管这种用例仍需要使用外部解释器,即Node.js) PyPy解释器在CPython之上运行,执行部分解释自身,然后将控制权移交给其RPython半部分进行翻译? 您可以使用已编译的PyPy解释器来编译PyPy,也可以使用CPython来代替。但是,由于PyPy现在有JIT,使用PyPy编译PyPy比使用CPython更快。(在大多数情况下,PyPy现在比CPython更快

1
这里所描述的是引导位,它会发出一个完全编译的PyPy,然后可以用来编译自身和随后的版本。 - alexgolec
我对PyPy或RPython的工作原理不太了解,但请在我的回答顶部的免责声明中查找一个链接,可能有助于理解其中的一部分。我了解到的简短部分是,Python代码生成一些内容,并使用GCC进行编译(这是一个非常密集的过程),但我确实知道使用带有JIT的PyPy比CPython要快得多。因此,您需要比仅仅使用PyPy更多的依赖项。一些可以“自我编译”的程序实际上依赖于GCC进行编译(或者像Node.js这样的其他系统),但就引导过程而言,流程通常是相同的。 - John Doe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接