cpython编译过程分析

Posted by Shi Hai's Blog on July 14, 2019

编译及执行的主要流程

python编译器执行过程

decode过程分析

cpython在编译源码过程会先判断source code使用的编码方案,因为不同的编码方式意味着不同的解码过程。 我这里用一个demo来演示一下python是如何来判断编码方案。

# 我有一个fab算法文件存放于 /home/shihai/test_fab.py中,具体内容如下所示:
def fab(max):
    n, a, b = 0, 0, 1
    L = []
    while n < max:
        L.append(b)
        a, b = b, a+b
        n = n + 1
    return L

for n in fab(5):
    print n

# 打开python编译器并调用tokenizen模块来查看此过程(python默认的编码格式utf-8)
$ ./python
Python 3.9.0a0 (heads/master:c8e7146, Jul 13 2019, 11:42:31)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>import tokenize
>>>fd = open("/home/shihai/test_fab.py", "rb")
>>>encoding, lines = tokenize.detect_encoding(fd.readline)
>>>encoding
'utf-8'
>>>lines
[b'\n', b'def fab(max):\n']

tokenize过程分析

tokenize是将代码解析成word的过程

# 用tokenize模块(tokenize模块是python的词汇扫描模块)进行代码解析过程后得到所有解析后的words
$ cat /home/shihai/test_fab.py| ./python -m tokenize -e
1,0-1,3:            NAME           'def'
1,4-1,7:            NAME           'fab'
1,7-1,8:            LPAR           '('
1,8-1,11:           NAME           'max'
1,11-1,12:          RPAR           ')'
1,12-1,13:          COLON          ':'
1,13-1,14:          NEWLINE        '\n'
2,0-2,4:            INDENT         '    '
2,4-2,5:            NAME           'n'
2,5-2,6:            COMMA          ','
2,7-2,8:            NAME           'a'
2,8-2,9:            COMMA          ','
2,10-2,11:          NAME           'b'
2,12-2,13:          EQUAL          '='
2,14-2,15:          NUMBER         '0'
2,15-2,16:          COMMA          ','

Tokens define

parse过程分析

parse过程是将words构建出parse tree(又称CST, 实际的抽象层次低于AST), 由node数据结构构成。

# x=3+2的过程会变成这个样子

Python 3.6.8 (default, Aug  7 2019, 17:28:10)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pprint import pprint
>>> import parser
>>> st = parser.expr('3+2')
>>> pprint(parser.st2list(st))
[258,
 [331,
  [305,
   [309,
    [310,
     [311,
      [312,
       [315,
        [316,
         [317,
          [318,
           [319,
            [320, [321, [322, [323, [324, [2, '3']]]]]],
            [14, '+'],
            [320, [321, [322, [323, [324, [2, '2']]]]]]]]]]]]]]]]],
 [4, ''],
 [0, '']]

node's access function

node's type

symtable 1

symtable 2

Abstract Syntax Tree

为啥要有CST到AST的转变过程呢?因为CST含有语法信息、token ID值、符号表信息,如果编译器直接从CST进行编译,整体的编译效率不高。CST到AST转换实际的过程是底抽象层次到高抽象层次的转换(实际目的是为了提高编译效率)。

# x = 3 + 2的过程
Python 3.6.8 (default, Aug  7 2019, 17:28:10)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ast
>>> vars(ast.parse("x=3+2").body[0])
{'targets': [<_ast.Name object at 0x7ffaa1663710>], 'value': <_ast.BinOp object at 0x7ffaaa6dd6d8>, 'lineno': 1, 'col_offset': 0}

# 实际的展开效果应该为:
Module(body=
    [Assing(
        targets=[Name(id='x',
            ctx=Store())],
        value=BinOp(left=Num(n=3),
                    op=Add(),
                    right=Num(n=2)
                    )
              )
    ])               

python asdl

compile过程

从AST解析到bytecode过程(核心过程是生成一个control-flow-graph图,这个我个人现在把这个过程理解成:是编译器如何串行解释字节码的过程;)

# 3+2的编译过程和反解析过程
>>> co = compile("3+2", "test.py", mode="eval")
>>> co.co_code
b'd\x02S\x00'
>>> import dis
>>> dis.dis(co.co_code)
          0 LOAD_CONST               2 (2)
          2 RETURN_VALUE

#查看代码的编码可以使用:echo "def func(): x = y + 2" | python -m dis`

最后的字节码其实是opcode(operation code),在python解释字节码时会将相关的字节码在转换为相关的c代码逻辑处理。 如:

# LOAD参数阶段找到local变更然后创建了一个PyObject对象入堆栈进行操作;
        case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            if (value == NULL) {
                format_exc_check_arg(tstate, PyExc_UnboundLocalError,
                                     UNBOUNDLOCAL_ERROR_MSG,
                                     PyTuple_GetItem(co->co_varnames, oparg));
                goto error;
            }
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }

python compile入口 python run bytecode

附录

cpython source code guide

python devguide