本系列作为本人@takooctopus深入学习python机制的记录,这个博客遵照着栖迟于一丘的博客上面的流程进行的,也包含我在实际查看源码时的感想,特此列出,表示感谢。
内建对象及其创建
我们定义一个简单的文件
{{- code -}}
我们使用下面的语句访问
{{- code -}}
最后我们得到最终的输出:
{{- code -}}
除开这些属性后,我们再去看这段代码的字节码:
{{- code -}}
上面就是foo.py所生成的字节码
可能用到的宏
{{- code -}}
指令解析
对应第一行
{{- code -}}
同样的,我们去[Python/ceval.c/_PyEval_EvalFrameDefault()]中寻找:
{{- code -}}
其上半句是取出常数的,GETITEM()相当于GETITEM(consts,1),并将这个对象压入时栈。
假设当前时栈对象PyFrameObject是f,consts就相当于f->f_code->co_consts。
在压入时栈后,虚拟机就需要为local命名空间中创造一个与其相映射的符号i。
{{- code -}}
我们可以看见names也就是f->f_code->co_names即符号表
ns是f->f_locals是本地命名空间,而NAMESPACE都是一个dict对象,保存了一个变量和值的映射关系。
第二行对于字符串变量的赋值也差不多,仅仅是索引的变化
{{- code -}}
第三行l = [] 创建了一个list
{{- code -}}
其中调用的是BUILD_LIST:
{{- code -}}
这里会新建一个list,并在如果参数为空的时候直接压入栈中,反之如果参数不为空,就会从栈中弹出元素,加入这个list。
第四行的d = {}会创建一个dict,
{{- code -}}
我们看BUILD_MAP所对应的case
{{- code -}}
当执行完这一段Code Block后,python需要返回一些值
{{- code -}}
将其实际返回的值放在retval中,POP()从运行时栈中拉出的,实际值是上次LOAD_CONST 2 (None)的,即最后返回一个None值。最后跳转至fast_block_end中。
{{- code -}}
将虚拟机状态设置为WHY_RETURN,进入最后end阶段
复杂内建对象的创建
我们考虑建立一个新的非空list或者dict
{{- code -}}
我们同样的采用下面的代码查看:
{{- code -}}
我们查看结果:
{{- code -}}
我们可以看见常量表co_const和符号表co_names,注意常量表中的小整数是共用的,大整数不会。
以及其字节码
{{- code -}}
注意,在创建list时,即调用BUILD_LIST时传入参数2,之后会从栈帧中读出两个元素。
而创建dict时,会先将其值压入栈,再将其key压入栈,最后才创建dict。
我们看创建函数BUILD_CONST_KEY_MAP
{{- code -}}
其他的一般表达式
这里继续使用了一段代码来看其实现:
{{- code -}}
{{- code -}}
以及其字节码
{{- code -}}
符号搜索
首先是一个LOAD_NAME这个指令,其会从符号表中取出变量值并压入栈中。
{{- code -}}
我们看到其采用了LGB原则去命名空间搜索变量。
数值运算
数值相加是指令BINARY_ADD,其会先将a和b取出「LOAD_NAME」压入时栈,再进行加法
{{- code -}}
会判断左右类型,如果为字符串就进行字符串拼接,如果不是就进行PyNumber_Add进行数值相加。
信息输出
而print()会先将print和c压入栈,再调用CALL_FUNCTION,其参数为1
{{- code -}}
我们可以看见其模拟了一个CPU的过程,使用call_function调用
其中会进行类型检查等一系列动作,最后调用builtin_print()函数输出