pyc文件的初步认识
搞清楚字节码被dis展示内容的各个位置含义后,下一步,是搞清楚pyc文件是什么。pyc文件是什么呢?它是Python存储字节码的地方,可以很方便的借助marshal模块看它的内容。
先来一个很简单的Python文件,试验一番。
# simple.py
print('Hello world')
用以下指令生成对应的pyc(如果这个py文件被import,那么也会生成对应的pyc文件)。
py -3 -m compileall simple.py
接着,可以用以下脚本,看到其中code_obj,通过dis出来的结果,与此前的试验结果一样了。
# test_marshal.py
import marshal, sys, dis
header_size = 8
if sys.version_info >= (3, 6):
header_size = 16
if sys.version_info >= (3, 7):
header_size = 16
with open('__pycache__/simple.cpython-39.pyc', 'rb') as f:
# 文件的头部长度,因Python版本差异有所不同
metadata = f.read(header_size)
print(f'header is: {metadata, len(metadata)}')
# 可以直接使用marshal模块将pyc文件load出来
code_obj = marshal.load(f)
dis.dis(code_obj)
我去看了一下Python中的源码,与上面函数是类似的,也是分为两块,忽略掉头部内容,其后便是字节码了。
// Python/pythonrun.c
static PyObject *
run_pyc_file(FILE *fp, const char *filename, PyObject *globals,
PyObject *locals, PyCompilerFlags *flags)
{
PyThreadState *tstate = _PyThreadState_GET();
PyCodeObject *co;
PyObject *v;
long magic;
long PyImport_GetMagicNumber(void);
magic = PyMarshal_ReadLongFromFile(fp);
if (magic != PyImport_GetMagicNumber()) {
if (!PyErr_Occurred())
PyErr_SetString(PyExc_RuntimeError,
"Bad magic number in .pyc file");
goto error;
}
/* Skip the rest of the header. */
(void) PyMarshal_ReadLongFromFile(fp);
(void) PyMarshal_ReadLongFromFile(fp);
(void) PyMarshal_ReadLongFromFile(fp);
if (PyErr_Occurred()) {
goto error;
}
v = PyMarshal_ReadLastObjectFromFile(fp);
if (v == NULL || !PyCode_Check(v)) {
Py_XDECREF(v);
PyErr_SetString(PyExc_RuntimeError,
"Bad code object in .pyc file");
goto error;
}
fclose(fp);
co = (PyCodeObject *)v;
v = run_eval_code_obj(tstate, co, globals, locals);
if (v && flags)
flags->cf_flags |= (co->co_flags & PyCF_MASK);
Py_DECREF(co);
return v;
error:
fclose(fp);
return NULL;
}
在网上搜了一下pyc文件的好处,有以下两点:
如果进行大量处理,使用pyc可节省编译时间;
与py文件执行效果相同,可以藏起来源码。 (原文链接)