从现代编译器的角度看,解释器和编译器的边界已经相当的模糊。我们后面的讨论说到的编译器就是Python的解释器,没有特别说明的指的是CPython的实现。

内存管理(Memory Management)

在讨论编译器的具体实现之前,我们不得不了解一下在这里面内存是如何井然有序地被分配的。为了让内存分配简单一些,一般我们都会建立一个Arena(不知道用中文怎么准确的表达)来管理内存。有了Arena我们就可以把内存集中在一起更容易地进行分配和销毁。在这里面没有了真正的内存的释放,也就是说内存的释放不会显式地对应系统的free(3)调用。也正是由于编译器每部每次内存的分配对于Arena都只是一次注册,释放编译器用到的所有内存也变得非常的简单:仅仅需要一次free(3)系统调用就可以达成。

一般来说,除非你是在研究或者开发编译器最核心的部分,才需要关注内存分配的问题。Python解释器里所有内存分配相关的代码都在Include/pyarena.h (https://github.com/python/cpython/blob/master/Include/pyarena.h)和 Python/pyarena.c(https://github.com/python/cpython/blob/master/Python/pyarena.c)可以找到。

(BTW,这里有一个我很久以前实现的一个定长内存池(Fixed Sized Pool)的源码,代码量很少,希望可以有助于大家了解内存池的概念:https://github.com/auxten/gkoAlloc/blob/master/memory.cpp)

PyArena_New()函数会帮我们创建一个新的Arena,并返回一个PyArena 结构体,这个结构体将会保存所有分配给此Arena的内存的指针。在编译器完成工作释放内存退出之前,都是由Arena负责对所有用到的内存进行“记账”。释放内存对应的API是PyArena_Free()。基本上,每个编译器只需要在最后的“战略大撤退”时调用它一次即可。

综上所述,内存池的设计初衷就是让我们在进行编译器的各项工作的时候不用去关心内存分配的各种细节。因此,我们研究编译器的绝大多数时候也可以先跳过这部分的内容。

唯一的一个例外就是在管理PyObject的时候。由于Python使用引用计数的方式进行内存垃圾回收,所以在Arena上增加了一些机制对它进行额外的支持,以便垃圾回收的时候能正确的处理每一个PyObject。虽然这种例外是很少见的,但是如果你在编译器的代码里分配一个PyObject,你必须通过调用PyArena_AddPyObject()来通知Arena增加相应的引用计数。

从语法树到抽象语法树(AST)

通过调用Python源码中的PyAST_FromNode()函数,语法树(参见Python/ast.c(https://github.com/python/cpython/blob/master/Python/ast.c#L883))被转换成抽象语法树(AST)。

PyAST_FromNode()函数对语法树进行树形遍历,在遍历的过程中动态创建出各种类型的AST节点。简单来说,就是遍历每一个节点,为每一个语法节点调用相对应的AST节点的构造函数,以及相关的支持函数,按照节点的组织方式对他们进行“连接”。

为了继续下面的内容,我们要简单介绍一下yacc,简单来说,yacc就是编译器的编译器:

yacc(Yet Another Compiler Compiler),是Unix/Linux上一个用来生成编译器的编译器(编译器代码生成器)。yacc生成的编译器主要是用C语言写成的语法解析器(Parser),需要与词法解析器Lex一起使用,再把两部分产生出来的C程序一并编译。yacc本来只在Unix系统上才有,但现时已普遍移植往Windows及其他平台。

yacc的输入是巴科斯范式(BNF)表达的语法规则以及语法规约的处理代码,Yacc输出的是基于表驱动的编译器,包含输入的语法规约的处理代码部分。

yacc是开发编译器的一个有用的工具,采用LALR(1)语法分析方法。

yacc最初由AT&T的Steven C. Johnson为Unix操作系统开发,后来一些兼容的程序如Berkeley Yacc,GNU bison,MKS yacc和Abraxas yacc陆续出现。它们都在原先基础上做了少许改进或者增加,但是基本概念是相同的。

由于所产生的解析器需要词法分析器配合,因此Yacc经常和词法分析器的产生器——一般就是Lex——联合使用。IEEE POSIX P1003.2标准定义了Lex和Yacc的功能和需求。

——摘自Wikipedia

需要格外注意的是,在语法规范和解析树中的节点之间没有自动化或符号连接。这点上Python解释器和yacc不同。

例如,就像我们处理“if”这个语法结构时,必须关注“:”这个token出现的位置,来决定“if”后面的条件语句的结束位置一样。跟踪我们正在使用解析树中哪个节点是十分必要的。

调用从解析树生成AST节点的函数都被命名为ast_for_xx,其中xx是函数处理的语法规则(但是,alias_for_import_name这个函数是个例外)。这些ast_for_xx函数会依次调用由ASDL语法定义的构造函数以及包含在Python/Python-ast.c(https://github.com/python/cpython/blob/master/Python/Python-ast.c)(由Parser/asdl_c.py(https://github.com/python/cpython/blob/master/Parser/asdl_c.py)生成)的构造函数以创建AST的节点。这样下来,所有的AST节点就全部存储在asdl_seq结构体中了。

创建和使用asdl_seq *类型的函数和宏都在 Python/asdl.c(https://github.com/python/cpython/blob/master/Python/asdl.c) 和 Include/asdl.h(https://github.com/python/cpython/blob/master/Include/asdl.h)中:

如果你正在处理Python的代码语句,代码语句的行号是一个绕不开的问题。目前来说,行号作为最后一个参数传递给每个stmt_ty函数的。这点在以后的Python解释器实现中可能会有改变。

作者丨Auxten

来源丨面向工资编程知乎专栏

交流qq群:238757010

浅析Python解释器的设计的更多相关文章

  1. 用 Python 编写的 Python 解释器

    Allison是Dropbox的工程师,在那里她维护着世界上最大的由Python客户组成的网络.在Dropbox之前,她是Recurse Center的引导师, … 她在北美的PyCon做过关于Pyt ...

  2. 《python解释器源码剖析》第9章--python虚拟机框架

    9.0 序 下面我们就来剖析python运行字节码的原理,我们知道python虚拟机是python的核心,在源代码被编译成字节码序列之后,就将有python的虚拟机接手整个工作.python虚拟机会从 ...

  3. 《python解释器源码剖析》第1章--python对象初探

    1.0 序 对象是python中最核心的一个概念,在python的世界中,一切都是对象,整数.字符串.甚至类型.整数类型.字符串类型,都是对象.换句话说,python中面向对象的理念观测的非常彻底,面 ...

  4. 深入浅析python中的多进程、多线程、协程

    深入浅析python中的多进程.多线程.协程 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源 ...

  5. 学写PEP,参与Python语言的设计

    如果你为Python写了一篇PEP,这篇PEP成功的被Python指导委员会接受了,那么以后你在吹牛皮的时候你就可以说我主导了Python语言某个特性的设计工作. -- 跬蟒 我就问你主导Python ...

  6. 浅析Python装饰器

    1.什么是装饰器 在介绍装饰器之前,我们先来思考一个问题:使用Python语言进行程序设计时,如果我们想扩展一个函数的功能,一般会怎么做呢? 比如,有一个名为print_info函数,当前该函数内只做 ...

  7. Python基础部分:2、 对计算机的认识和python解释器

    目录 一.计算机五大组成部分 1.控制器 2.运算器 3.储存器 4.输入设备 5.输出设备 二.计算机三大核心硬件 1.cpu 2.内存 3.硬盘 三.操作系统 四.编程与编程语言 1.编程语言 2 ...

  8. python入门-python解释器执行

    最近由于公司需要,接触了python这门神奇的语言,给我的感觉就是开发快速和代码简洁. 开始还是先罗列一下解释性语言和编译性语言的差别吧0.0!   编译性语言:是在程序运行前,需要专门的一个编译过程 ...

  9. python学习笔记-python解释器

    刚开始学习python,首先要了解一下python解释器. 什么是python解释器? 编写python代码保存后,我们会得到一个以.py为扩展名的文本文件.要运行此文件,就需要python解释器去执 ...

随机推荐

  1. 代码管理 ,git 命令整理

    //先要理解这四个概念,这是一个提交代码的流动轨迹:1.工作区(编辑器)-经过add到2- 2.暂存区 (缓存)- 经过commit3-3.本地仓库 (本地项目)-经过 push4-4.远程仓库 (线 ...

  2. 大白话Vue源码系列(01):万事开头难

    阅读目录 Vue 的源码目录结构 预备知识 先捡软的捏 Angular 是 Google 亲儿子,React 是 Facebook 小正太,那咱为啥偏偏选择了 Vue 下手,一句话,Vue 是咱见过的 ...

  3. 使用邮件激活授权/ LightningChart license

    在无网络连接的情况下,可以采用邮件的方式激活授权. 先打开License Manager,然后选 Activate/Deactivate via email, 如下图所示: 此邮件将自动发送到 lic ...

  4. 修改 docker image 安装目录 (解决加载大image时报错:"no space left on device")

    修改 docker image 安装目录 (解决加载大image时报错:"no space left on device" ) 基于Ubuntu16.04 docker版本: 17 ...

  5. Sencha Cmd 6 和 Ext JS 6 指南文档(部分官方文档中文翻译)

    近期组织了几个程序员网友,正在翻译一部分官方的Sencha Cmd 6 和 Ext JS 6 指南文档. 眼下还没翻译完,大家能够先看看 Sencha Cmd 6 和 Ext JS 6 指南文档  ( ...

  6. java.lang.IllegalStateException: attempt to re-open an already-closed object

    attempt to re-open an already-closed object 字面理解,试图再次打开已经关闭的对象.这是我在操作sqlited的时候出现的错误, 我在一个activity里面 ...

  7. lufylegend练习(1)帧速率

    近期发现一个HTML开源游戏引擎,感觉还不错http://lufylegend.com/ 可是没有基础的同学.看起来费劲.所以打算边学边记笔记,说明都在凝视中 <!DOCTYPE html> ...

  8. Apriori算法的C++实现

    Apriori是经典的购物篮分析算法.该算法用SQL实现难度较大,所以考虑用C++实现. 花了两天,代码例如以下.原创转载请注明出处 //Apriori.c #include<iostream& ...

  9. Redis 订阅发布 - Jedis实现

    Redis 订阅发布 - Jedis实现 我想到使用Redis的订阅发布模式是用来解决推送问题的-. 对于概念性的叙述,多多少少还是要提一下的: ​ 什么是Redis发布订阅?Redis发布订阅是一种 ...

  10. 为eclipse离线安装hibernate tools插件

    工具/原料   eclipse luna jboss hibernate tools 方法/步骤     下载hibernate tools插件   在eclipse根目录下创建myplugins和l ...