from importlib.util import MAGIC_NUMBER import struct import time from types import CodeType import marshal from opcode import opmap HEADER = MAGIC_NUMBER + b"\x00" * 4 # 时间随便写 HEADER += struct.pack("<I", int(time.time())) # 大小随便写 HEADER += struct.pack("<I", 30) # 构建 PyCodeObject code = CodeType( 0, # co_argcount 0, # co_posonlyargcount 0, # co_kwonlyargcount 3, # co_nlocals 1, # co_stacksize 0, # co_flags bytes([ # a = 1 分为两步 # 第一步:先通过 LOAD_CONST 将常量加载进来 # 因此指令是 LOAD_CONST,然后参数是 0 # 表示加载常量池中索引为 0 的常量 opmap["LOAD_CONST"], 0, # 第二步:通过 STORE_NAME 将常量和符号绑定起来 # 参数是 0,表示和符号表中索引为 0 的符号进行绑定 opmap["STORE_NAME"], 0, # b = 2 opmap["LOAD_CONST"], 1, opmap["STORE_NAME"], 1, # c = 3 opmap["LOAD_CONST"], 2, opmap["STORE_NAME"], 2, # 结尾要 LOAD 一个 None,然后返回 opmap["LOAD_CONST"], 3, opmap["RETURN_VALUE"] ]), # co_code (1, 2, 3, None), # co_consts ("a", "b", "c"), # co_names (), # co_varnames "build_pyc.py", # co_filename "<module>", # co_name 1, # co_firstlineno b"", # co_lnotab (), # freevars () # cellvars ) # pyc 文件内容 pyc_content = HEADER + marshal.dumps(code) # 生成 pyc 文件 with open("build_pyc.pyc", "wb") as f: f.write(pyc_content) # 然后加载生成的 pyc 文件 from importlib.machinery import SourcelessFileLoader mod = SourcelessFileLoader( "build_pyc", "build_pyc.pyc" ).load_module() print(mod) # <module 'build_pyc' from 'build_pyc.pyc'> print(mod.a) # 1 print(mod.b) # 2 print(mod.c) # 3
怎么样,是不是很有趣呢?
pyc 文件的写入
下面通过源码来查看 pyc 文件的写入过程,既然要写入,那么肯定要有文件句柄。
//位置:Python/marshal.c //FILE是 C 自带的文件句柄 //可以把WFILE看成是FILE的包装 typedef struct { FILE *fp; //文件句柄 //下面的字段在写入信息的时候会看到 int error; int depth; PyObject *str; char *ptr; char *end; char *buf; _Py_hashtable_t *hashtable; int version; } WFILE;
首先是写入 magic number、创建时间和文件大小,它们会调用 PyMarshal_WriteLongToFile 函数进行写入:
void PyMarshal_WriteLongToFile(long x, FILE *fp, int version) { //magic number、创建时间和文件大小,只是一个整数 //在写入的时候,使用char [4]来保存 char buf[4]; //声明一个WFILE类型变量wf WFILE wf; //内存初始化 memset(&wf, 0, sizeof(wf)); //初始化内部成员 wf.fp = fp; wf.ptr = wf.buf = buf; wf.end = wf.ptr + sizeof(buf); wf.error = WFERR_OK; wf.version = version; //调用w_long将x、也就是版本信息或者时间写到wf里面去 w_long(x, &wf); //刷到磁盘上 w_flush(&wf); }
所以该函数只是初始化了一个 WFILE 对象,真正写入则是调用的 w_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); }
w_long 则是调用 w_byte 将 x 逐个字节地写到文件里面去。
而写入 PyCodeObject 对象则是调用 PyMarshal_WriteObjectToFile,它实际又会调用 w_object 进行写入。
static void w_object(PyObject *v, WFILE *p) { char flag = '\0'; p->depth++; if (p->depth > MAX_MARSHAL_STACK_DEPTH) { p->error = WFERR_NESTEDTOODEEP; } else if (v == NULL) { w_byte(TYPE_NULL, p); } else if (v == Py_None) { w_byte(TYPE_NONE, p); } else if (v == PyExc_StopIteration) { w_byte(TYPE_STOPITER, p); } else if (v == Py_Ellipsis) { w_byte(TYPE_ELLIPSIS, p); } else if (v == Py_False) { w_byte(TYPE_FALSE, p); } else if (v == Py_True) { w_byte(TYPE_TRUE, p); } else if (!w_ref(v, &flag, p)) w_complex_object(v, flag, p); p->depth--; }
可以看到本质上还是调用了 w_byte,但这仅仅是一些特殊的对象。如果是列表、字典之类的数据,那么会调用 w_complex_object,也就是代码中的最后一个 else if 分支。
w_complex_object 这个函数的源代码很长,我们看一下整体结构,具体逻辑就不贴了,后面会单独截取一部分进行分析。
static void w_complex_object(PyObject *v, char flag, WFILE *p) { Py_ssize_t i, n; //如果是整数的话,执行整数的写入逻辑 if (PyLong_CheckExact(v)) { //...... } //如果是浮点数的话,执行浮点数的写入逻辑 else if (PyFloat_CheckExact(v)) { if (p->version > 1) { //...... } else { //...... } } //如果是复数的话,执行复数的写入逻辑 else if (PyComplex_CheckExact(v)) { if (p->version > 1) { //...... } else { //...... } } //如果是字节序列的话,执行字节序列的写入逻辑 else if (PyBytes_CheckExact(v)) { //...... } //如果是字符串的话,执行字符串的写入逻辑 else if (PyUnicode_CheckExact(v)) { if (p->version >= 4 && PyUnicode_IS_ASCII(v)) { //...... } else { //...... } } else { //...... } } //如果是元组的话,执行元组的写入逻辑 else if (PyTuple_CheckExact(v)) { //...... } //如果是列表的话,执行列表的写入逻辑 else if (PyList_CheckExact(v)) { //...... } //如果是字典的话,执行字典的写入逻辑 else if (PyDict_CheckExact(v)) { //...... } //如果是集合的话,执行集合的写入逻辑 else if (PyAnySet_CheckExact(v)) { //...... } //如果是PyCodeObject对象的话 //执行PyCodeObject对象的写入逻辑 else if (PyCode_Check(v)) { //...... } //如果是Buffer的话,执行Buffer的写入逻辑 else if (PyObject_CheckBuffer(v)) { //...... } else { W_TYPE(TYPE_UNKNOWN, p); p->error = WFERR_UNMARSHALLABLE; } }
源代码虽然长,但是逻辑非常单纯,就是对不同的对象、执行不同的写动作,然而其最终目
本文地址:百科问答频道 https://www.neebe.cn/wenda/903398_2.html,易企推百科一个免费的知识分享平台,本站部分文章来网络分享,本着互联网分享的精神,如有涉及到您的权益,请联系我们删除,谢谢!