蚊章汇 - 解释器
type: channel
link: https://linuxcn.bwsl.wang/tag/%E8%A7%A3%E9%87%8A%E5%99%A8/
items: 六大 Python 解释器
items: 让我们做个简单的解释器（三）
items: 让我们做个简单的解释器（二）
items: 让我们做个简单的解释器（一）
items: 用 Python 实现 Python 解释器

六大 Python 解释器
type: item
date: 2023-10-18T07:23:26+08:00
description: 观察你的代码在其他解释器下运行的表现或许是一项有趣的尝试。作为最受欢迎的编程语言之一，Python 需要一个解释器来执行其代码所定义的命令。与其他可直接编译成机器代码的语言不同，Python 代码需要解释器读取它并把它转译给进行相关操作的 CPU。那么，哪些解释器有哪些呢？本文将对其中几种进行介绍。解释器简介提到 Python 解释器，我们通常会想到 /usr/bin/python 这个二进制文件。它使你能够执行 .py 文件。然而，解释操作仅仅是其中一环。在 Python 代码真正被 CPU 执行之前，都需要经过以下四个步骤：词法分析 - 将人类编写的源代码转换为一序列逻辑实体，被称为 词法标记 lexical token 。解析 - 解析器会检查词法标记的语法和语义规则生成 抽象语法树 abstract syntax tree （AST）。编译 - 编译器会根据 AST 创建 Python 字节码，这些字节码由非常基础的，和平台无关的指令组成。解释 - 解释器处理字节码并执行特定的操作。如你所见，在任何实质性的操作发生之前，我们需要走过这些步骤。这也解释了深入研究不同解释器的重要性。1、CPython作为 Python 的参考实现，CPython 默认地被许多系统所采用。如其名称所示，CPython 是用 C 语言编写的。这也意味着，我们可以 以 C 语言编写扩展，从而让 Python 打通到广泛使用的 C 语言库代码。CPython 广泛应用于各种平台，包括 ARM 和 RISC。然而，作为 Python 的参考实现，CPython 更注重精细的优化，而非运行速度。2、PystonPyston 是一个从 CPython 解释器衍生出的分支，其中实现了性能优化。该项目定位自己为标准 CPython 解释器在处理大型、真实世界应用时的替代品，并有可能加速高达 30%。由于缺乏兼容的二进制包，Pyston 在下载过程中需要重新编译。3、PyPy采用了 RPython 编写的 PyPy 是一个专为 Python 配备的 即时（JIT） 编译器，RPython 是 Python 的一个静态类型的子集。不同于 CPython 解释器，PyPy 对源代码进行编译，生成 CPU 可直接执行的机器码。PyPy 是 Python 开发者的实验室，在这里他们能更容易地测试新特性。相较于 CPython，PyPy 的执行速度更快。由于 JIT 编译器的特性，长时间运行的应用更能从缓存中受益。PyPy 可以被视为 CPython 的有效替代。虽然其中存在一些缺点，大部分的 C 扩展模块在 PyPy 中也得到支持，但运行速度会相对慢一些。PyPy 扩展模块使用 Python（而不是 C）编写，这使 JIT 编译器能够对其进行优化。只要你的应用程序不依赖于不兼容的模块，PyPy 就是替换 CPython 的理想选择。你可以在项目官网找到一个专门的页面，详细描述 PyPy 与 CPython 的不同之处：PyPy 与 CPython 的差异4、RustPython顾名思义，RustPython 是一个由 Rust 编写的 Python 解释器。尽管 Rust 如今还是一个相对年轻的编程语言，但因其优良特性已逐步受到开发者的推崇，甚至被视为 C 和 C++ 的可能接班人。默认情况下，RustPython 的行为与 CPython 的解释器类似，但它也可以选择启用 JIT 编译器。值得一提的是，Rust 工具链能直接编译为 WebAssembly ，进而允许在浏览器中全面运行解释器。你可以在 这里 看到它的在线演示。5、Stackless PythonStackless Python 自称是 Python 编程语言的增强版本。该项目基本上是 CPython 解释器衍生的一个项目，其为该语言添加了微线程、通道和调度器。微线程可以帮助你将代码组织成可以并行运行的 “ 小任务 tasklet ”。这与采用 greenlet 模块的绿色线程模型相似。通道可以用作 “小任务” 之间的双向通信。Stackless Python 的一个知名用户是大型多人在线角色扮演游戏 Eve Online。6、Micro Python如果你的目标平台是微控制器，那么 MicroPython 将是你的首选。它是一种极简的实现，只需要 16kB 的内存和 256kB 的存储空间。由于其主要面向的是嵌入式环境，MicroPython 的标准库只包含 CPython 丰富的 STL 的一部分。对于开发和测试，或者作为轻量级替代品，MicroPython 也可以在普通的 x86 和 x64 系统上运行。MicroPython 支持 Linux、Windows，以及多种微控制器。性能就其设计而言…

让我们做个简单的解释器（三）
type: item
date: 2018-04-09T17:01:00+08:00
description: 早上醒来的时候，我就在想：“为什么我们学习一个新技能这么难？”我不认为那是因为它很难。我认为原因可能在于我们花了太多的时间，而这件难事需要有丰富的阅历和足够的知识，然而我们要把这样的知识转换成技能所用的练习时间又不够。拿游泳来说，你可以花上几天时间来阅读很多有关游泳的书籍，花几个小时和资深的游泳者和教练交流，观看所有可以获得的训练视频，但你第一次跳进水池的时候，仍然会像一个石头那样沉入水中，要点在于：你认为自己有多了解那件事都无关紧要 —— 你得通过练习把知识变成技能。为了帮你练习，我把训练放在了这个系列的 第一部分 和 第二部分 了。当然，你会在今后的文章中看到更多练习，我保证 ：)好，让我们开始今天的学习。到现在为止，你已经知道了怎样解释像 “7 + 3” 或者 “12 - 9” 这样的两个整数相加减的算术表达式。今天我要说的是怎么解析（识别）、解释有多个数字相加减的算术表达式，比如 “7 - 3 + 2 - 1”。文中的这个算术表达式可以用下面的这个语法图表示：什么是 语法图 syntax diagram ？ 语法图 是对一门编程语言中的语法规则进行图像化的表示。基本上，一个语法图就能告诉你哪些语句可以在程序中出现，哪些不能出现。语法图很容易读懂：按照箭头指向的路径。某些路径表示的是判断，有些表示的是循环。你可以按照以下的方式读上面的语法图：一个 term 后面可以是加号或者减号，接着可以是另一个 term，这个 term 后面又可以是一个加号或者减号，后面又是一个 term，如此循环。从字面上你就能读懂这个图片了。或许你会奇怪，“term” 是什么、对于本文来说，“term” 就是个整数。语法图有两个主要的作用：它们用图形的方式表示一个编程语言的特性（语法）。它们可以用来帮你写出解析器 —— 你可以根据下列简单规则把图片转换成代码。你已经知道，识别出记号流中的词组的过程就叫做 解析。解释器或者编译器执行这个任务的部分叫做 解析器。解析也称为 语法分析，并且解析器这个名字很合适，你猜的对，就是 语法分析器。根据上面的语法图，下面这些表达式都是合法的：33 + 47 - 3 + 2 - 1因为算术表达式的语法规则在不同的编程语言里面是很相近的，我们可以用 Python shell 来“测试”语法图。打开 Python shell，运行下面的代码：>>> 3 3 >>> 3 + 4 7 >>> 7 - 3 + 2 - 1 5 意料之中。表达式 “3 + ” 不是一个有效的数学表达式，根据语法图，加号后面必须要有个 term （整数），否则就是语法错误。然后，自己在 Python shell 里面运行：>>> 3 + File "<stdin>", line 1 3 + ^ SyntaxError: invalid syntax 能用 Python shell 来做这样的测试非常棒，让我们把上面的语法图转换成代码，用我们自己的解释器来测试，怎么样？从之前的文章里（第一部分 和 第二部分）你知道 expr 方法包含了我们的解析器和解释器。再说一遍，解析器仅仅识别出结构，确保它与某些特性对应，而解释器实际上是在解析器成功识别（解析）特性之后，就立即对表达式进行评估。以下代码片段显示了对应于图表的解析器代码。语法图里面的矩形方框（term）变成了 term 方法，用于解析整数，expr 方法和语法图的流程一致：def term(self): self.eat(INTEGER) def expr(self): # 把当前标记设为从输入中拿到的第一个标记 self.current_token = self.get_next_token() self.term() while self.current_token.type in (PLUS, MINUS): token = self.current_token if token.type == PLUS: self.eat(PLUS) self.term() elif token.type == MINUS: self.eat(MINUS) self.term() 你能看到 expr 首先调用了 term 方法。然后 expr 方法里面的 while 循环可以执行 0 或多次。在循环里面解析器基于标记做出判断（是加号还是减号）。花一些时间，你就知道，上述代码确实是遵循着语法图的算术表达式流程。解析器并不解释任何东西：如果它识别出了一个表达式，它就静默着，如果没有识别出来，就会抛出一个语法错误。改一下 expr 方法，加入解释器的代码：def term(self): """Return an INTEGER token value""" token = self.current_token…

让我们做个简单的解释器（二）
type: item
date: 2018-04-07T18:31:05+08:00
description: 在一本叫做 《高效思考的 5 要素》 的书中，作者 Burger 和 Starbird 讲述了一个关于他们如何研究 Tony Plog 的故事，他是一位举世闻名的交响曲名家，为一些有才华的演奏者开创了一个大师班。这些学生一开始演奏复杂的乐曲，他们演奏的非常好。然后他们被要求演奏非常基础简单的乐曲。当他们演奏这些乐曲时，与之前所演奏的相比，听起来非常幼稚。在他们结束演奏后，老师也演奏了同样的乐曲，但是听上去非常娴熟。差别令人震惊。Tony 解释道，精通简单音符可以让人更好的掌握复杂的部分。这个例子很清晰 —— 要成为真正的名家，必须要掌握简单基础的思想。故事中的例子明显不仅仅适用于音乐，而且适用于软件开发。这个故事告诉我们不要忽视繁琐工作中简单基础的概念的重要性，哪怕有时候这让人感觉是一种倒退。尽管熟练掌握一门工具或者框架非常重要，了解它们背后的原理也是极其重要的。正如 Palph Waldo Emerson 所说：“如果你只学习方法，你就会被方法束缚。但如果你知道原理，就可以发明自己的方法。”有鉴于此，让我们再次深入了解解释器和编译器。今天我会向你们展示一个全新的计算器，与 第一部分 相比，它可以做到：处理输入字符串任意位置的空白符识别输入字符串中的多位整数做两个整数之间的减法（目前它仅能加减整数）新版本计算器的源代码在这里，它可以做到上述的所有事情：# 标记类型 # EOF （end-of-file 文件末尾）标记是用来表示所有输入都解析完成 INTEGER, PLUS, MINUS, EOF = 'INTEGER', 'PLUS', 'MINUS', 'EOF' class Token(object): def __init__(self, type, value): # token 类型: INTEGER, PLUS, MINUS, or EOF self.type = type # token 值: 非负整数值, '+', '-', 或无 self.value = value def __str__(self): """String representation of the class instance. Examples: Token(INTEGER, 3) Token(PLUS '+') """ return 'Token({type}, {value})'.format( type=self.type, value=repr(self.value) ) def __repr__(self): return self.__str__() class Interpreter(object): def __init__(self, text): # 客户端字符输入， 例如. "3 + 5", "12 - 5", self.text = text # self.pos 是 self.text 的索引 self.pos = 0 # 当前标记实例 self.current_token = None self.current_char = self.text[self.pos] def error(self): raise Exception('Error parsing input') def advance(self): """Advance the 'pos' pointer and set the 'current_char' variable.""" self.pos += 1 if self.pos > len(self.text) - 1: self.current_char = None # Indicates end of input else: self.current_char = self.text[self.pos] def skip_whitespace(self): while self.current_char is not None and self.current_char.isspace(): self.advance() def integer(self): """Return a (multidigit) integer consumed from the input.""" result = '' while self.current_char is not None and self.current_char.isdigit(): result += self.current_char self.advance() return int(result) def get_next_token(self): """Lexical analyzer (also known as sca…

让我们做个简单的解释器（一）
type: item
date: 2018-03-04T17:41:00+08:00
description: “如果你不知道编译器是怎么工作的，那你就不知道电脑是怎么工作的。如果你不能百分百确定，那就是不知道它们是如何工作的。” --Steve Yegge就是这样。想一想。你是萌新还是一个资深的软件开发者实际上都无关紧要：如果你不知道 编译器 compiler 和 解释器 interpreter 是怎么工作的，那么你就不知道电脑是怎么工作的。就这么简单。所以，你知道编译器和解释器是怎么工作的吗？我是说，你百分百确定自己知道他们怎么工作吗？如果不知道。或者如果你不知道但你非常想要了解它。不用担心。如果你能坚持跟着这个系列做下去，和我一起构建一个解释器和编译器，最后你将会知道他们是怎么工作的。并且你会变成一个自信满满的快乐的人。至少我希望如此。为什么要学习编译器和解释器？有三点理由。要写出一个解释器或编译器，你需要有很多的专业知识，并能融会贯通。写一个解释器或编译器能帮你加强这些能力，成为一个更厉害的软件开发者。而且，你要学的技能对编写软件非常有用，而不是仅仅局限于解释器或编译器。你确实想要了解电脑是怎么工作的。通常解释器和编译器看上去很魔幻。你或许不习惯这种魔力。你会想去揭开构建解释器和编译器那层神秘的面纱，了解它们的原理，把事情做好。你想要创建自己的编程语言或者特定领域的语言。如果你创建了一个，你还要为它创建一个解释器或者编译器。最近，兴起了对新的编程语言的兴趣。你能看到几乎每天都有一门新的编程语言横空出世：Elixir，Go，Rust，还有很多。好，但什么是解释器和编译器？解释器 和 编译器 的任务是把用高级语言写的源程序翻译成其他的格式。很奇怪，是不是？忍一忍，稍后你会在这个系列学到到底把源程序翻译成什么东西。这时你可能会奇怪解释器和编译器之间有什么区别。为了实现这个系列的目的，我们规定一下，如果有个翻译器把源程序翻译成机器语言，那它就是 编译器。如果一个翻译器可以处理并执行源程序，却不用把它翻译器机器语言，那它就是 解释器。直观上它看起来像这样：我希望你现在确信你很想学习构建一个编译器和解释器。你期望在这个教程里学习解释器的哪些知识呢？你看这样如何。你和我一起为 Pascal 语言的一个大子集做一个简单的解释器。在这个系列结束的时候你能做出一个可以运行的 Pascal 解释器和一个像 Python 的 pdb 那样的源代码级别的调试器。你或许会问，为什么是 Pascal？一方面，它不是我为了这个系列而提出的一个虚构的语言：它是真实存在的一门编程语言，有很多重要的语言结构。有些陈旧但有用的计算机书籍使用 Pascal 编程语言作为示例（我知道对于选择一门语言来构建解释器，这个理由并不令人信服，但我认为学一门非主流的语言也不错 :））。这有个 Pascal 中的阶乘函数示例，你将能用自己的解释器解释代码，还能够用可交互的源码级调试器进行调试，你可以这样创造：program factorial; function factorial(n: integer): longint; begin if n = 0 then factorial := 1 else factorial := n * factorial(n - 1); end; var n: integer; begin for n := 0 to 16 do writeln(n, '! = ', factorial(n)); end. 这个 Pascal 解释器的实现语言会使用 Python，但你也可以用其他任何语言，因为这里展示的思想不依赖任何特殊的实现语言。好，让我们开始干活。准备好了，出发！你会从编写一个简单的算术表达式解析器，也就是常说的计算器，开始学习解释器和编译器。今天的目标非常简单：让你的计算器能处理两个个位数相加，比如 3+5。下面是你的计算器的源代码——不好意思，是解释器：# 标记类型 # # EOF （end-of-file 文件末尾）标记是用来表示所有输入都解析完成 INTEGER, PLUS, EOF = 'INTEGER', 'PLUS', 'EOF' class Token(object): def __init__(self, type, value): # token 类型: INTEGER, PLUS, MINUS, or EOF self.type = type # token 值: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, '+', 或 None self.value = value def __str__(self): """String representation of the class instance. Examples: Token(INTEGER, 3) Token(PLUS '+') """ return…

用 Python 实现 Python 解释器
type: item
date: 2016-09-08T22:16:18+08:00
description: Allison 是 Dropbox 的工程师，在那里她维护着这个世界上最大的 Python 客户端网络之一。在去 Dropbox 之前，她是 Recurse Center 的协调人, 是这个位于纽约的程序员深造机构的作者。她在北美的 PyCon 做过关于 Python 内部机制的演讲，并且她喜欢研究奇怪的 bug。她的博客地址是 akaptur.com。介绍Byterun 是一个用 Python 实现的 Python 解释器。随着我对 Byterun 的开发，我惊喜地的发现，这个 Python 解释器的基础结构用 500 行代码就能实现。在这一章我们会搞清楚这个解释器的结构，给你足够探索下去的背景知识。我们的目标不是向你展示解释器的每个细节---像编程和计算机科学其他有趣的领域一样，你可能会投入几年的时间去深入了解这个主题。Byterun 是 Ned Batchelder 和我完成的，建立在 Paul Swartz 的工作之上。它的结构和主要的 Python 实现（CPython）差不多，所以理解 Byterun 会帮助你理解大多数解释器，特别是 CPython 解释器。（如果你不知道你用的是什么 Python，那么很可能它就是 CPython）。尽管 Byterun 很小，但它能执行大多数简单的 Python 程序（这一章是基于 Python 3.5 及其之前版本生成的字节码的，在 Python 3.6 中生成的字节码有一些改变）。Python 解释器在开始之前，让我们限定一下“Pyhton 解释器”的意思。在讨论 Python 的时候，“解释器”这个词可以用在很多不同的地方。有的时候解释器指的是 Python REPL，即当你在命令行下敲下 python 时所得到的交互式环境。有时候人们会或多或少的互换使用 “Python 解释器”和“Python”来说明从头到尾执行 Python 代码的这一过程。在本章中，“解释器”有一个更精确的意思：Python 程序的执行过程中的最后一步。在解释器接手之前，Python 会执行其他 3 个步骤：词法分析，语法解析和编译。这三步合起来把源代码转换成 代码对象 （ code object ） ，它包含着解释器可以理解的指令。而解释器的工作就是解释代码对象中的指令。你可能很奇怪执行 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 解释器是一个 虚拟机 （ virtual machine ） ，是一个模拟真实计算机的软件。我们这个虚拟机是 栈机器 （ stack machine ） ，它用几个栈来完成操作（与之相对的是 寄存器机器 （ register machine ） ，它从特定的内存地址读写数据）。Python 解释器是一个 字节码解释器 （ bytecode interpreter ） ：它的输入是一些称作 字节码 （ bytecode ） 的指令集。当你写 Python 代码时，词法分析器、语法解析器和编译器会生成 代码对象 （ code object…