CPython开发实战:魔改lambda函数(三)

315 阅读3分钟

本次实战内容是受到Javascript的启发,将Python为人诟病已久的lambda函数改成Javascript风格的箭头函数,效果如下:

捕获.JPG

上一章讲到语法分析器的编写和AST树的生成,本章讲解AST树的遍历和符号表的生成。

9. 在Python/symtable.c文件第1994行添加如下代码:

case ArrowLbd_kind: {
        if (e->v.ArrowLbd.args->defaults)
            VISIT_SEQ(st, expr, e->v.ArrowLbd.args->defaults);
        if (e->v.ArrowLbd.args->kw_defaults)
            VISIT_SEQ_WITH_NULL(st, expr, e->v.ArrowLbd.args->kw_defaults);
        if (!symtable_enter_block(st, &_Py_ID(arrowlbd),
                                  FunctionBlock, (void *)e,
                                  e->lineno, e->col_offset,
                                  e->end_lineno, e->end_col_offset))
            VISIT_QUIT(st, 0);
        VISIT(st, arguments, e->v.ArrowLbd.args);
        VISIT(st, expr, e->v.ArrowLbd.body);
        if (!symtable_exit_block(st))
            VISIT_QUIT(st, 0);
        break;
    }

AST树生成后进入CFG生成阶段。

flow4.jpg

控制流图(Control Flow Graph,CFG)是用来表示程序运行过程的树状图。每一个节点代表一个block,block是一段连续的指令集合,且这些指令都是顺序执行。在CFG生成阶段需要遍历两次AST树,第一次为生成各block,第二次为通过跳转指令连接各block。

考虑下面一段代码:

if x > 10 and x < 20:
    f1()
    f2()
else:
    g()

该代码会在CFG生成阶段的第一次遍历中被拆分成四个block:第一个block为处理x>10,第二个block为处理x<20,第三个block为调用f1()f2(),第四个block为调用g()

在第二次遍历中会连接这四个block:当第一个block为真后跳转至第二个block,否则跳转至第四个block;当第二个block为真后跳转至第三个block,否则跳转至第四个block。树状图如下:

cfg.JPG

在每一个block中,程序需要管理相应的符号。常见的Python的符号类型有以下几种:

  • co_consts: 常量,比如上述代码片段中的10、20
  • co_names: 变量名字面量,比如上述代码片段中的x、f1、f2和g
  • co_varnames: 变量名,且只在当前frame内生效
  • co_cellvars: 变量名,在内层frame内生效,比如闭包外声明闭包中使用的变量
  • co_freevars: 变量名,在外层frame内生效,和上面相反。

在CFG生成阶段第一次遍历之前,需要获取符号表。该过程需要遍历两次AST树,第一次生成各block中的上述符号,第二次处理符号的可见性,比如外部符号对内部可见。本步骤的代码即该过程的第一次遍历。

AST树需要通过递归实现遍历。本步骤的代码位于symtable_visit_expr函数内,该函数处理AST中的Python表达式节点。VISIT_*宏可以处理函数的跳转,比如VISIT(st, arguments, ...)可以跳转到symtable_visit_arguments函数处理AST中的参数节点。

该代码先判断参数是否有默认参数,然后调用symtable_enter_block函数进入新的block,因为lambda函数也是一种闭包,而闭包的调用必定需要跳转。在该block内,程序通过两个VISIT宏分别递归处理lambda表达式的参数和body。最后通过symtable_exit_block函数跳出该block。

第二次遍历处理符号的可见性是原有代码,不需要改造。

10. 再次在命令行中运行PCBuild/build.bat --regen生成全局字符串

因为该代码中用到_Py_ID宏。该宏是引用一个全局字符串arrowlbd作为标识。但是这个字符串是第一次在此处定义,所以需要运行自动化生成脚本生成该全局字符串。

运行git status可以看到本次改动的文件。

symtable.JPG

pycore_global_strings.h中可以看到利用宏生成的全局字符串结构体。

STRUCT_FOR_ID(arrowlbd)

本章完成了符号表的生成,下一章将介绍CFG完整的遍历并生成Python字节码。