cpython教程_教你阅读 Cpython 的源码(二)(2)|python教程|python入门|python教程

https://www.xin3721.com/eschool/pythonxin3721/

模块以及C API在Python中生成它们。

在深入研究AST的C实现之前,理解一个简单的Python代码的AST是很有用的。

为此,这里有一个名为instaviz的简单应用程序。可以在Web UI中显示AST和字节码指令(稍后我们将介绍)。

小插曲

这里我需要说下,因为我按照原文的例子去照着做,发现根本就运行不起来,所以我就和大家说我的做法。

首先,我们不能通过pip的方式去安装运行,而是从github上把他的源码下载下来,然后在其文件下创建一个文件。

该程序需要在Python3.6+的环境下运行,包含3.6。

1.下载

https://github.com/tonybaloney/instaviz.git

2.写脚本

随意命名,比如example.py,代码如下

import instaviz

def example():

a = 1

b = a + 1

return b

if __name__ == "__main__":

instaviz.show(example)

3.目录结构如下

4.修改文件web.py

将原来的server_static函数和home函数用下面的代码替换

@route("/static/")

def server_static(filename):

return static_file(filename, root="./static/")

@route("/", name="home")

@jinja2_view("home.html", template_lookup=["./templates/"])

def home():

global data

data["style"] = HtmlFormatter().get_style_defs(".highlight")

data["code"] = highlight(

"".join(data["src"]),

PythonLexer(),

HtmlFormatter(

linenos=True, linenostart=data["co"].co_firstlineno, linespans="src"

),

)

return data

5.运行

好了,现在可以运行example.py文件了,运行之后会生成一个web服务(因为这个模块是基于bottle框架的),然后浏览器打开

http://localhost:8080/

6.展示页面

好了,我们继续原文的思路。

这里就到了展示图了

左下图是我们声明的example函数,表示为抽象语法树。

树中的每个节点都是AST类型。它们位于ast模块中,继承自_ast.AST。

一些节点具有将它们链接到子节点的属性,与CST不同,后者具有通用子节点属性。

例如,如果单击中心的Assign节点,则会链接到b = a + 1行:

它有两个属性:

targets是要分配的名称列表。它是一个列表,因为你可以使用解包来使用单个表达式分配多个变量。

value是要分配的值,在本例中是BinOp语句,a+ 1。

如果单击BinOp语句,则会显示相关属性:

left:运算符左侧的节点

op:运算符,在本例,是一个Add节点(+)

right:运算符右侧的节点

看一下图就了解了

在C中编译AST并不是一项简单的任务,因此Python/ast.c模块超过5000行代码。

有几个入口点,构成AST的公共API的一部分。

在词法分析(Lexing)和句法分析(Parsing)的最后一节中,我们讲到了对PyAST_FromNodeObject()的调用。在此阶段,Python解释器进程以node * tree的格式创建了一个CST。然后跳转到Python/ast.c中的PyAST_FromNodeObject(),你可以看到它接收node * tree,文件名,compiler flags和PyArena。

此函数的返回类型是定义在文件Include/Python-ast.h的mod_ty函数。

mod_ty是Python中5种模块类型之一的容器结构:

1.Module

2.Interactive

3.Expression

4.FunctionType

5.Suite

在Include/Python-ast.h中,你可以看到Expression类型需要一个expr_ty类型的字段。expr_ty类型也是在Include/Python-ast.h中定义。

enum _mod_kind {Module_kind=1, Interactive_kind=2, Expression_kind=3,

FunctionType_kind=4, Suite_kind=5};

struct _mod {

enum _mod_kind kind;

union {

struct {

asdl_seq *body;

asdl_seq *type_ignores;

} Module;

struct {

asdl_seq *body;

} Interactive;

struct {

expr_ty body;

} Expression;

struct {

asdl_seq *argtypes;

expr_ty returns;

} FunctionType;

struct {

asdl_seq *body;

} Suite;

} v;

};

AST类型都列在Parser/Python.asdl中,你将看到所有列出的模块类型,语句类型,表达式类型,运算符和结构。本文档中的类型名称与AST生成的类以及ast标准模块库中指定的相同类有关。

Include/Python-ast.h中的参数和名称与Parser/Python.asdl中指定的参数和名称直接相关:

-- ASDL's 5 builtin types are:

-- identifier, int, string, object, constant

module Python

{

mod = Module(stmt* body, type_ignore *type_ignores)

| Interactive(stmt* body)

| Expression(expr body)

| FunctionType(expr* argtypes, expr returns)

因为C头文件和结构在那里,因此Python/ast.c程序可以快速生成带有指向相关数据的指针的结构。查看PyAST_FromNodeObject(),你可以看到它本质上是一个switch语句,根据TYPE(n)的不同作出不同操作。TYPE()是AST用来确定具体语法树中的节点是什么类型的核心函数之一。在使用PyAST_FromNodeObject()的情况下,它只是查看第一个节点,因此它只能是定义为Module,Interactive,Expression,FunctionType的模块类型之一。TYPE()的结果要么是符号(symbol)类型要么是标记(token)类型。

对于file_input,结果应该是Module。Module是一系列语句,其中有几种类型。

遍历n的子节点和创建语句节点的逻辑在ast_for_stmt()内。如果模块中只有1个语句,则调用此函数一次,如果有多个语句,则调用循环。然后使用PyArena返回生成的Module。

对于eval_input,结果应该是Expression,CHILD(n,0)(n的第一个子节点)的结果传递给ast_for_testlist(),返回expr_ty类型。然后使用PyArena将此expr_ty发送到Expression()以创建表达式节点,然后作为结果传回:

mod_ty

PyAST_FromNodeObject(const node *n, PyCompilerFlags *flags,

PyObject *filename, PyArena *arena)

{

...

switch (TYPE(n)) {

case file_input:

stmts = _Py_asdl_seq_new(num_stmts(n), arena);

if (!stmts)

goto out;

for (i = 0; i < NCH(n) - 1; i++) {

ch = CHILD(n, i);

if (TYPE(ch) == NEWLINE)

continue;

REQ(ch, stmt);

num = num_stmts(ch);

if (num == 1) {

s = ast_for_stmt(&c, ch);

if (!s)

goto out;

asdl_seq_SET(stmts, k++, s);

}

else {

ch = CHILD(ch, 0);

REQ(ch, simple_stmt);

for (j = 0; j < num; j++) {

s = ast_for_stmt(&c, CHILD(ch, j * 2));

if (!s)

goto out;

asdl_seq_SET(stmts, k++, s);

}

}

}

/* Type ignores are stored under the ENDMARKER in file_input. */

...

res = Module(stmts, type_ignores, arena);

break;

case eval_input: {

expr_ty testlist_ast;

/* XXX Why not comp_for here? */

testlist_ast = ast_for_testlist(&c, CHILD(n, 0));

if (!testlist_ast)

goto out;

res = Expression(testlist_ast, arena);

break;

}

case single_input:

...

break;

case func_type_input:

...

...

return res;

}

在ast_for_stmt()函数里,也有一个switch语句,它会判断每个可能的语句类型(simple_stmt,compound_stmt等),以及用于确定节点类的参数的代码。

再来一个简单的例子,2**42的4次幂。这个函数首先得到ast_for_atom_expr(),这是我们示例中的数字2,然后如果有一个子节点,则返回原子表达式.如果它有多个字节点,使用Pow操作符之后,左节点是一个e(2),右节点是一个f(4)。

static expr_ty

ast_for_power(struct compiling *c, const node *n)

{

/* power: atom trailer* ('**' factor)*

*/

expr_ty e;

REQ(n, power);

e = ast_for_atom_expr(c, CHILD(n, 0));

if (!e)

return NULL;

if (NCH(n) == 1)

return e;

if (TYPE(CHILD(n, NCH(n) - 1)) == factor) {

expr_ty f = ast_for_expr(c, CHILD(n, NCH(n) - 1));

if (!f)

return NULL;

e = BinOp(e, Pow, f, LINENO(n), n->n_col_offset,

n->n_end_lineno, n->n_end_col_offset, c->c_arena);

}

return e;

}

如果使用instaviz模块查看上面的函数

>>> def foo():

2**4

>>> import instaviz

>>> instaviz.show(foo)

在UI中,你还可以看到其相应的属性:

总之,每个语句类型和表达式都是由一个相应的ast_for_*()函数来创建它。

参数在Parser/Python.asdl中定义,并通过标准库中的ast模块公开出来。

如果表达式或语句具有子级,则它将在深度优先遍历中调用相应的ast_for_*子函数。

结论

CPython的多功能性和低级执行API使其成为嵌入式脚本引擎的理想候选者。

你将看到CPython在许多UI应用程序中使用,例如游戏设计,3D图形和系统自动化。

解释器过程灵活高效,现在你已经了解它的工作原理。

在这一部分中,我们了解了CPython解释器如何获取输入(如文件或字符串),并将其转换为逻辑抽象语法树。我们还没有处于可以执行此代码的阶段。接下来,我们将继续深入,了将抽象语法树转换为CPU可以理解的一组顺序命令的过程。

-后续-

更多技术内容,关注公众号:python学习开发

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值