从现代编译器的角度看,解释器和编译器的边界已经相当的模糊。我们后面的讨论说到的编译器就是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. 【Java入门提高篇】Day6 Java内部类——成员内部类

    内部类是什么,简单来说,就是定义在类内部的类(一本正经的说着废话). 一个正经的内部类是长这样的: public class Outer { class Inner{ } } 这是为了演示而写的类,没 ...

  2. Solr7使用Oracle数据源导入+中文分词

    安装目录假设为#solr_home,本文的#solr_home为apps/svr/solr 1. 在#solr_home/server/solr下新建文件夹,假设为mjd 2. 将#solr_home ...

  3. canvas 从初级到XX 1# 部分非基础原生API的使用 [初级向]

    标题canvas 从初级到XX,XX是因为本文随机都可能会太监,并不会支持到入土.请慎重的往下看. 对于canvas的介绍,随处都可以找到,也就不啰嗦太多了.就直奔主题了. 先看一段代码,以及实现的效 ...

  4. json解析—Gson以及GsonFormat插件的运用

    最近开始慢慢做毕业设计了,遇到一个功能是获取天气预报的,我选择的是和风天气的api,返回的是JSON数据,所以遇到了解析JSON的问题 首先简单说下JSON,JSON(JavaScript Objec ...

  5. 3.python元组与列表

    Python的元组与列表类似,同样可通过索引访问,支持异构,任意嵌套.不同之处在于元组的元素不能修改.元组使用小括号,列表使用方括号. 创建元组 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开 ...

  6. 来腾讯云开发者实验室 学习.NET Core 2.0

    腾讯云开发者实验室为开发者提供了一个零门槛的在线实验平台,开发者实验室提供的能力: 零门槛扫码即可免费领取实验机器,支持使用自有机器参与,实验完成后支持保留实验成果: 在线 WEB IDE 支持 sh ...

  7. JAVA入门[7]-Mybatis generator(MBG)自动生成mybatis代码

    一.新建测试项目 新建Maven项目MybatisDemo2,修改pom.xml引入依赖.dependencies在上节基础上新增 <dependency> <groupId> ...

  8. 掀起Azure AD的盖头来——深入理解Microsoft Graph应用程序和服务权限声明

    作者:陈希章 发表于 2017年7月12日 引子 这是一篇计划外的文章.我们都知道要进行Microsoft Graph的开发的话,需要进行应用程序注册.这个在此前我已经有专门的文章写过了.但这里存在一 ...

  9. java枚举细节

     1.在没有枚举之前,我们如果需要一些常量,比如说,我们想用一些常量来代替订单的几种状态,如已下单未付款.已付款未发货.已发货未确认收货.已收货未评价.已评价.我们会定义一个用来装常量的类,比如: p ...

  10. ansible编译httpd playbook示例

    以下是playbook的内容.它的处理流程是: 1.先在本地下载apr,apr-util,httpd共3个.tar.gz文件. 2.解压这3个文件. 3.安装pcre和pcre-devel依赖包. 4 ...