当前位置: 首页 >> 程序设计 >> Python源码研究(2):执行过程和基本架构
 

Python源码研究(2):执行过程和基本架构

作者:      来源:     发表时间:2007-03-25     浏览次数:      字号:    

文中的Python程序的CST看起来大致是这样的:

 

图中省略了很多细节,因为是CST的关系,分析过程中有大量冗余信息,主要是每一步通过DFA分析的结点都列在这棵树里面了。由于篇幅的关系就不把整棵树画出来了。

那么CST是怎么生成的呢?在PyParser_ParseFileFlags中:

node *

PyParser_ParseFileFlags(FILE *fp, const char *filename, grammar *g, int start,

            char *ps1, char *ps2, perrdetail *err_ret, int flags)

{

    struct tok_state *tok;

 

    initerr(err_ret, filename);

 

    if ((tok = PyTokenizer_FromFile(fp, ps1, ps2)) == NULL) {

        err_ret->error = E_NOMEM;

        return NULL;

    }

    tok->filename = filename;

    if (Py_TabcheckFlag || Py_VerboseFlag) {

        tok->altwarning = (filename != NULL);

        if (Py_TabcheckFlag >= 2)

            tok->alterror++;

    }

 

 

    return parsetok(tok, g, start, err_ret, flags);

}

PyParser_ParserFileFlags首先创建tok_state,也就是词法分析器的对象,之后调用parsetok。Parsetok的代码量稍多,这里就不全部列出来了。主干代码如下:

static node *

parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,

     int flags)

{

    parser_state *ps;

    node *n;

    int started = 0, handling_import = 0, handling_with = 0;

 

    ps = PyParser_New(g, start);

 

    for (;;) {

        type = PyTokenizer_Get(tok, &a, &b);

        if ((err_ret->error = PyParser_AddToken(ps, (int)type, str, tok->lineno, col_offset, &(err_ret->expected))) != E_OK) {

            if (err_ret->error != E_DONE) {

                PyObject_FREE(str);

                err_ret->token = type;

            }                

            break;

        }

    }

 

    if (err_ret->error == E_DONE) {

        n = ps->p_tree;

        ps->p_tree = NULL;

    }

    else

        n = NULL;

 

    PyParser_Delete(ps);

 

    if (n == NULL) {

        // error processing

    } else if (tok->encoding != NULL) {

        node* r = PyNode_New(encoding_decl);

        if (!r) {

            err_ret->error = E_NOMEM;

            n = NULL;

            goto done;

        }

        r->n_str = tok->encoding;

        r->n_nchildren = 1;

        r->n_child = n;

        tok->encoding = NULL;

        n = r;

    }

 

done:

    PyTokenizer_Free(tok);

 

    return n;

}

里面最重要的是两个函数调用:

  1. PyTokenizer_Get,是用来进行词法分析的,把源程序分解为Token的序列(比如变量名,运算符,关键字等都属于Token)
  2. PyParser_AddToken,把Token加入到DFA状态机中进行语法分析,根据当前的状态和输入的Token,根据跳转表跳转到不同的状态,并依照这个过程生成CST

由于篇幅有限,这个两个函数的实现细节会在以后的文章详细分析。

在PyParser_ParserFileFlags得到了语法树之后,PyAST_FromNode会将CST转换为AST,存入mod_ty中。

typedef _mod *mod_ty;

 

struct _mod {

enum _mod_kind kind;

union {

struct {

asdl_seq *body;

} Module;

 

struct {

asdl_seq *body;

} Interactive;

 

struct {

expr_ty body;

} Expression;

 

struct {

asdl_seq *body;

} Suite;

 

} v;

};

_mod是AST的根结点,代表整个Module。在Python-ast.h中定义着所有AST结点的结构,mod_ty和expr_ty也在其中,后者显然代表着一个表达式。

adsl_seq代表一个变长的指针数组,结构定义如下:

typedef struct {

int size;

void *elements[1];

} asdl_seq;

这个结构稍微有些特殊的是elements在struct中只有一个元素。其实这个struct可以支持任意多个元素,正因为如此,普通的定义方法是不行的。因此这里只定义一个元素,然后在分配计算实际的大小(会比adsl_seq这个结构要大),然后访问元素的时候直接用elements,因为C/C++是不会检查越界的。这种做法在C/C++系统编程中还是比较常见的。

Adsl_seq粗看上去只保存了void *,也就是说具体类型信息丢失了,那么当要遍历整个树的时候是如何做的呢?其实当遍历到某个结点(比如Module/Expression)的时候,便可以确定该结点所支持的子结点是什么类型,然后直接转强制转换就可以了。还有一种情况是在结点中直接记录的是具体的有类型的AST结点,比如expr_ty,就更加容易了。

文中Python代码对应的AST大致如下:

 

同样的,也省略了一些细节。可以看到,最终的结果要比CST要少很多,比较接近源代码本来的样子。

当PyParser_ASTFromFile执行完毕之后,回到PyRun_FileExFlags,执行下一步,调用run_mod,也就是执行这颗AST所代表的程序。

static PyObject *

run_mod(mod_ty mod, const char *filename, PyObject *globals, PyObject *locals,

     PyCompilerFlags *flags, PyArena *arena)

{

    PyCodeObject *co;

    PyObject *v;

    co = PyAST_Compile(mod, filename, flags, arena);

    if (co == NULL)

        return NULL;

    v = PyEval_EvalCode(co, globals, locals);

    Py_DECREF(co);

    return v;

}

Run_mod非常直接,分两步走:

  1. 编译AST,生成PyCodeObject对象,也就是ByteCode。
  2. 调用PyEval_EvalCode执行PyCodeObject,也就是通过VM直接执行bytecode。如果是执行.pyc/.pyo代码的话,直接从文件中读出信息创建好PyCodeObject对象就可以直接执行了,也是调用这个函数。

执行完PyEval_EvalCode之后可以看到55被打印出来了。

 

总结一下,Python执行程序的过程总共有以下几步:

  1. Tokenizer进行词法分析,把源程序分解为Token
  2. Parser根据Token创建CST
  3. CST被转换为AST
  4. AST被编译为字节码
  5. 执行字节码

后面的文章中我会对1~5这5步分别进行详细分析,今天就先写到这里。 

[1] [2]

责任编辑 webmaster

 
 
 
 
 
评论更多>>
 
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •