您的位置:首页 > 编程语言 > Python开发

用_Python_实现_Python_解释器

2017-06-09 00:00 316 查看
摘要:Allison是Dropbox的工程师,在那里她维护着这个世界上最大的Python客户端网络之一。在去Dropbox之前,她是RecurseCenter的协调人,是这个位于纽约的程序员深造机构的作者。

Allison是Dropbox的工程师,在那里她维护着这个世界上最大的Python客户端网络之一。在去Dropbox之前,她是RecurseCenter的协调人,是这个位于纽约的程序员深造机构的作者。她在北美的PyCon做过关于Python内部机制的演讲,并且她喜欢研究奇怪的bug。她的博客地址是akaptur.com。



介绍

Byterun是一个用Python实现的Python解释器。随着我对Byterun的开发,我惊喜地的发现,这个Python解释器的基础结构用500行代码就能实现。在这一章我们会搞清楚这个解释器的结构,给你足够探索下去的背景知识。我们的目标不是向你展示解释器的每个细节---像编程和计算机科学其他有趣的领域一样,你可能会投入几年的时间去深入了解这个主题。

Byterun是NedBatchelder和我完成的,建立在PaulSwartz的工作之上。它的结构和主要的Python实现(CPython)差不多,所以理解Byterun会帮助你理解大多数解释器,特别是CPython解释器。(如果你不知道你用的是什么Python,那么很可能它就是CPython)。尽管Byterun很小,但它能执行大多数简单的Python程序(这一章是基于Python3.5及其之前版本生成的字节码的,在Python3.6中生成的字节码有一些改变)。

Python解释器

在开始之前,让我们限定一下“Pyhton解释器”的意思。在讨论Python的时候,“解释器”这个词可以用在很多不同的地方。有的时候解释器指的是PythonREPL,即当你在命令行下敲下python时所得到的交互式环境。有时候人们会或多或少的互换使用“Python解释器”和“Python”来说明从头到尾执行Python代码的这一过程。在本章中,“解释器”有一个更精确的意思:Python程序的执行过程中的最后一步。

在解释器接手之前,Python会执行其他3个步骤:词法分析,语法解析和编译。这三步合起来把源代码转换成代码对象codeobject,它包含着解释器可以理解的指令。而解释器的工作就是解释代码对象中的指令。

你可能很奇怪执行Python代码会有编译这一步。Python通常被称为解释型语言,就像Ruby,Perl一样,它们和像C,Rust这样的编译型语言相对。然而,这个术语并不是它看起来的那样精确。大多数解释型语言包括Python在内,确实会有编译这一步。而Python被称为解释型的原因是相对于编译型语言,它在编译这一步的工作相对较少(解释器做相对多的工作)。在这章后面你会看到,Python的编译器比C语言编译器需要更少的关于程序行为的信息。

Python的Python解释器

Byterun是一个用Python写的Python解释器,这点可能让你感到奇怪,但没有比用C语言写C语言编译器更奇怪的了。(事实上,广泛使用的gcc编译器就是用C语言本身写的)你可以用几乎任何语言写一个Python解释器。

用Python写Python既有优点又有缺点。最大的缺点就是速度:用Byterun执行代码要比用CPython执行慢的多,CPython解释器是用C语言实现的,并做了认真优化。然而Byterun是为了学习而设计的,所以速度对我们不重要。使用Python最大优势是我们可以仅仅实现解释器,而不用担心Python运行时部分,特别是对象系统。比如当Byterun需要创建一个类时,它就会回退到“真正”的Python。另外一个优势是Byterun很容易理解,部分原因是它是用人们很容易理解的高级语言写的(Python!)(另外我们不会对解释器做优化——再一次,清晰和简单比速度更重要)

构建一个解释器

在我们考察Byterun代码之前,我们需要从高层次对解释器结构有一些了解。Python解释器是如何工作的?

Python解释器是一个虚拟机virtualmachine,是一个模拟真实计算机的软件。我们这个虚拟机是栈机器stackmachine,它用几个栈来完成操作(与之相对的是寄存器机器registermachine,它从特定的内存地址读写数据)。

Python解释器是一个字节码解释器bytecodeinterpreter:它的输入是一些称作字节码bytecode的指令集。当你写Python代码时,词法分析器、语法解析器和编译器会生成代码对象codeobject让解释器去操作。每个代码对象都包含一个要被执行的指令集——它就是字节码——以及还有一些解释器需要的信息。字节码是Python代码的一个中间层表示intermediaterepresentation:它以一种解释器可以理解的方式来表示源代码。这和汇编语言作为C语言和机器语言的中间表示很类似。

微型解释器

为了让说明更具体,让我们从一个非常小的解释器开始。它只能计算两个数的和,只能理解三个指令。它执行的所有代码只是这三个指令的不同组合。下面就是这三个指令:

LOAD_VALUE

ADD_TWO_VALUES

PRINT_ANSWER

我们不关心词法、语法和编译,所以我们也不在乎这些指令集是如何产生的。你可以想象,当你写下7+5,然后一个编译器为你生成那三个指令的组合。如果你有一个合适的编译器,你甚至可以用Lisp的语法来写,只要它能生成相同的指令。

假设



7+5

生成这样的指令集:



what_to_execute={

"instructions":[("LOAD_VALUE",0),#thefirstnumber

("LOAD_VALUE",1),#thesecondnumber

("ADD_TWO_VALUES",None),

("PRINT_ANSWER",None)],

"numbers":[7,5]}

Python解释器是一个栈机器stackmachine,所以它必须通过操作栈来完成这个加法(见下图)。解释器先执行第一条指令,LOAD_VALUE,把第一个数压到栈中。接着它把第二个数也压到栈中。然后,第三条指令,ADD_TWO_VALUES,先把两个数从栈中弹出,加起来,再把结果压入栈中。最后一步,把结果弹出并输出。



栈机器

LOAD_VALUE这条指令告诉解释器把一个数压入栈中,但指令本身并没有指明这个数是多少。指令需要一个额外的信息告诉解释器去哪里找到这个数。所以我们的指令集有两个部分:指令本身和一个常量列表。(在Python中,字节码就是我们所称的“指令”,而解释器“执行”的是代码对象。)

为什么不把数字直接嵌入指令之中?想象一下,如果我们加的不是数字,而是字符串。我们可不想把字符串这样的东西加到指令中,因为它可以有任意的长度。另外,我们这种设计也意味着我们只需要对象的一份拷贝,比如这个加法7+7,现在常量表"numbers"只需包含一个[7]。

你可能会想为什么会需要除了ADD_TWO_VALUES之外的指令。的确,对于我们两个数加法,这个例子是有点人为制作的意思。然而,这个指令却是建造更复杂程序的轮子。比如,就我们目前定义的三个指令,只要给出正确的指令组合,我们可以做三个数的加法,或者任意个数的加法。同时,栈提供了一个清晰的方法去跟踪解释器的状态,这为我们增长的复杂性提供了支持。

现在让我们来完成我们的解释器。解释器对象需要一个栈,它可以用一个列表来表示。它还需要一个方法来描述怎样执行每条指令。比如,LOAD_VALUE会把一个值压入栈中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: