编译及执行的主要流程
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 ','
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, '']]
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)
)
)
])
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