浅析Python解释器的设计(一)

一些铺垫(扯淡)

历史上,在Python 2.4以及之前的版本,py代码的执行,也就是从源码到bytecode分为两步:

  1. 解析py源码成为分析树 (Parser/pgen.c)
  2. 基于分析树优化缩减bytecode (Python/compile.c)

也是由于Guido van Rossum大叔的历史局限性,Python当年就被实现成这么一个样子了,运行的也挺好。后来随着LLVM党的崛起,大家逐渐认识到这种模式的局限性,后面还会说。

正经说起来,一个标准的“现代”解释器(编译器)应该是“介事儿”的:

  1. 把py源码解析成分析树 (Parser/pgen.c)
  2. 把分析树转化成抽象语法树AST(Abstract Syntax Tree) (Python/ast.c)
  3. 把AST转化成CFG(Control Flow Graph) (Python/compile.c)
  4. 基于Control Flow Graph优化生成bytecode (Python/compile.c)

从Python 2.5开始,CPython“改邪归正”走上了现代编译器的康庄大道。简单来说就是把原先的第二步拆解成3个步骤。后面我们会着重简要讲一下后面三步的过程:

这里不会阐述太多关于语法解析如何工作的内容,除了必须的有助于我们了解编译过程的内容。如果想要了解Python语法解析的细节,还是建议你去直接阅读CPython的源码。

语法分析树

2.5版以后的Python的语法解释器是基于“龙书”的LL(1) parser的一个标准的实现。Python的语法定义可以在CPython源码的Grammar/Grammar (https://github.com/python/cpython/blob/master/Grammar/Grammar)看到:

各种token值的定义都在Include/graminit.h (https://github.com/python/cpython/blob/master/Include/graminit.h) 。组成语法解析树的node * structs定义在Include/node.h(https://github.com/python/cpython/blob/master/Include/node.h)。

从node结构体里查询是由一组定义在 Include/token.h 的一组宏实现的:

  • CHILD(node *, int)

Returns the nth child of the node using zero-offset indexing

  • RCHILD(node *, int)

Returns the nth child of the node from the right side; use negative numbers!

  • NCH(node *)

Number of children the node has

  • STR(node *)

String representation of the node; e.g., will return : for a COLON token

  • TYPE(node *)

The type of node as specified in Include/graminit.h

  • REQ(node *, TYPE)

Assert that the node is the type that is expected

  • LINENO(node *)

retrieve the line number of the source code that led to the creation of the parse rule; defined in Python/ast.c

我们可以通过while语法的定义了解到上面所有的示例:

这段定义表示这个节点将会有如下等式成立:

子节点的编号将会是4或者7,取决于’while’后面是否有一个’else’声明。通过下面的代码我们可以确定后面的第一个 ‘:’ 是否存在以及代表什么。

抽象语法树 (AST)

抽象语法树(AST)是程序结构的一种高抽象层次的表达,有了它我们并再不需要源代码的存在了。它可以认为是源代码等价的一种抽象表达。CPython采用Zephyr抽象语法定义语言(ASDL)(http://www.cs.princeton.edu/research/techreps/TR-554-97)来描述AST。

Python的AST定义可以在源代码的Parser/Python.asdl (https://github.com/python/cpython/blob/master/Parser/Python.asdl)找到:

每种AST节点(声明,表达式,一些特殊类型例如:列表推导式、异常处理handler)都被ASDL描述定义。大多数的AST定义都对应一种特定的源码结构,例如一个’if’定义或者一个属性查找。这些定义都是和具体实现Python的语言无关的,也就是说无论CPython、PyPy、Jyphon甚至IronPython,都是用的同样的ASDL。

下面的Python ASDL结构展示了Python的函数定义相关语法结构和实现:

上面的示例描述了3种不同的语法结构:def函数定义、return声明、yield声明。这三种语法结构的语法声明被用 ‘|’ 分割。它们接受的参数类型和个数各不相同。

每个参数定义前面还带了不同的修饰符(这点和正则很像)来说明需要多少个参数:

  • ‘?’说明这个参数是可有可无的(0~1个);
  • ‘*’说明这个参数的数量是0或者更多(≥ 0);
  • 没有修饰符表示这个参数是必须且只能有一个的。

例如,函数定义(def)接受一个 identifier 作为函数名,’arguments’作为参数,0或者多个stmt作为函数体,0或者多个expr作为装饰器。

请注意这里的’arguments’,不像大家可能会认为的和stmt一样,它是作为一个node类型会由一个单独的AST定义所表示。

所有的这三种语法结构类型都有一个 ‘attributes’ 参数,所以 ‘attributes’ 前面没有 ‘|’ 。

上面的ASDL会被翻译生成如下的C语言结构体类型:

一同被生成的还有一些构造函数,它们会给这些语法结构分配一个stmt_ty结构体,并附带一些必要的初始化。’kind’ enum字段表明下面声明的是哪种union。FunctionDef() 的构造函数将会吧’kind’设置成 FunctionDef_kind 并且初始化 ‘name’, ‘args’, ‘body’, 和 ‘attributes’ 字段。

                                                                    运维自动化开班啦!

理论结合实战,使学员既可掌握快速从零构建一套实用、完整、可扩展的运维自动化平台:

  • 深度结合使用流行的zabbix、Ansible、Git、Docker、Rancher、ELK等开源框架与工具, 以应用最广泛的django框架为基础,构建一站式运维自动化平台
  •  通过深度剖析与二次开发定制,结合REST API、运维流程化、运维可视化、运维平台化 思想来构造企业级的运维自动化解决方案
  • 在老师带领下大战Zabbix、CMDB、集群自动化部署上线、ELK日志大数据分析、Docker 容器管理平台等多个最新实战,天天实战,招招实用

课程时间:5期运维自动化  8月6日开班

上课模式:网络直播班    线下面授班

询QQ(1)979950755    小夏

   QQ(2)279312229    ada

报名咨询电话:010-56061309

课程大纲:http://www.51reboot.com/course/devops/

文章分类 未分类

发表评论

电子邮件地址不会被公开。

在线交流

数百位业内高手和同行在等你交流
Reboot运维开发分享