本系列作为本人@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()
函数输出