VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • 教你阅读 Cpython 的源码(二)(2)

模块以及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/<filename>")
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学习开发



相关教程
关于我们--广告服务--免责声明--本站帮助-友情链接--版权声明--联系我们       黑ICP备07002182号