解释器篇
code对象和pyc文件
PyCodeObject和pyc文件其实只是源文件编译后的不同体现,前者是内存中的结果,后者是磁盘存在的结构。
一个PyCodeObject的边界在于一个新的作用域,所以模块,函数,类都有其PyCodeObject的结构。这里有一个重要的概念就是名称空间,代表符号的上下文,名称空间一个套一个形成了一条名称空间链。
Field | Content |
---|---|
co_argcount | Code Block 的位置参数的个数,比如说一个函数的位置参数个数 |
co_nlocals | Code Block 中局部变量的个数,包括位置参数的个数 |
co_stacksize | 执行该段Code Block需要的栈空间 |
co_flags | 标示 |
co_code | 字节码 |
co_consts | PyTupleObject,保存Code Block中所有的常量 |
co_names | PyTupleObject,保存Code Block中所有的符号 |
co_varnames | Code Block中局部变量名集合 |
co_freevars | python闭包需要用到的东西 |
co_cellvars | Code Block中内嵌函数使用的局部变量名集合 |
co_filename | Code Block完整路径 |
co_name | Code Block名称 |
co_firstlineno | Code Block在.py的起始位置 |
co_lnotab | 字节码与.py文件中source code行号对应关系 |
PyCodeObject对象中的co_consts包含python文件中定义的常量对象,常量对象即是那些非PyString对象以外的所有对象,字符串则是使用索引对应到co_names。co_names会记录所有的符号,co_varnames会记录所有局部变量符号。
pyc的生成
其实了解过rpc应该指导,rpc序列化确定消息边界常用的方式是使用长度前缀法或者特殊符号分割法。类似http协议的序列化head部分就是采取特殊符号分割法,而pyc的序列化方式是长度前缀法(用类型类似可以表达长度的概念)。
大致的写入过程如下:
static void write_compiled_module(PyCodeObject *co, char *cpathname, long mtime){FILE *fp;fp = open_exclusive(cpathname);PyMarsha1_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION);PyMarsha1_WriteLongToFile(mtime, fp, Py_MARSHAL_VERSION);PyMarsha1_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION);fflush(fp);fclose(fp);
}PyMarsha1_WriteObjectToFile过程则是解析不同的类型,然后调用不同的函数进行序列化,long类型的序列化如下:
static void w_long(long x, WFILE *p){w_byte((char)(x & 0xff), p);w_byte((char)((x>>8) & 0xff), p);w_byte((char)((x>>16) & 0xff), p);w_byte((char)((x>>24) & 0xff), p);
}if(PyInt_check(v)){w_byte(TYPE_INT, p);w_long(x, p);
}对于module里面的PyCode则是保存在co_consts中,分析这个字段如果发现PyCode对象,则递归进去。
复制代码
字节码
通过opcode模块的opmap可以看到字节码对应的值。
python某些字节码需要使用到参数,那么这个关系是用下面的方式体现的。
#define HAVE_ARGUMENT 90
#define HAS_ARG(op) ((op) >= HAVE_ARGUMENT)
复制代码
通过dis模块可以读取出可视化的python字节码内容。