PyCodeObject && magic number
PyCodeObject
python在执行python文件时,首先执行的动作是编译,编译时python会收集源代码中一切有用的信息,并生成PyCodeObject对象,这个对象在python运行结束时有时还会被保存在pyc文件中。python在对代码进行编译时,会对代码的每个code block生成一个PyCodeObject对象,即对每个命名空间生成一个code block。
struct PyCodeObject {
PyObject_HEAD
int co_argcount;
int co_posonlyargcount;
int co_kwonlyargcount;
int co_nlocals;
int co_stacksize;
int co_flags;
int co_firstlineno;
PyObject *co_code;
PyObject *co_consts;
PyObject *co_names;
PyObject *co_varnames;
PyObject *co_freevars;
PyObject *co_cellvars;
...
...
...
};
co_argcount:python函数的位置参数的个数。
>>> def func(a,b,c): ... pass ... >>> print(func.__code__.co_argcount) 3 >>>
co_posonlyargcount:只能通过位置参数传递的参数个数。
>>> def func(a,b,c,/,d,e): ... pass ... >>> print(func.__code__.co_posonlyargcount) 3 >>>
co_kwonlyargcount:只能通过关键词传递参数的个数。
>>> def func(a,b,c,*,d,e): ... pass ... >>> print(func.__code__.co_kwonlyargcount) 2 >>>
co_nlocals:代码块中局部变量的个数,也包括参数。
>>> def func(a,b,c,*,d,e): ... f = 1 ... g = 2 ... >>> print(func.__code__.co_nlocals) 7 >>>
co_stacksize:执行该段代码需要的栈空间。
>>> def func(num): ... if num == 1: ... return 1 ... return num * func(num - 1) ... >>> print(func.__code__.co_stacksize) 4 >>>
co_flags:记录当前代码块的标识,生成器的 flags 特征值为 32,即 100000。当编译器在解释一个方程并产生字节码时,遇到了yield关键字后解释器就会将100000加至函数的__code__.co_flags标识中,然后通过比特运算获取flags中包含的种种信息。
>>> def func(num): ... x = yield num ... y = yield x ... return y ... >>> print(func.__code__.co_flags & 32) 32 >>>
co_firstlineno:代码块对应文件的起始行。
>>> def func(a,b,c,*,d,e): ... pass ... >>> print(func.__code__.co_firstlineno) 1 >>>
*co_code:编译得到的字节码指令序列。
>>> def func(num): ... print(num) ... >>> print(func.__code__.co_code) b't\x00|\x00\x83\x01\x01\x00d\x00S\x00' >>>
*co_consts:常量池,一个元组对象,保存代码块中的所有常量。
>>> def func(a,b,c): ... x = 1 ... f = False ... t = True ... >>> print(func.__code__.co_consts) (None, 1, False, True) >>>
*co_names:一个元组,保存代码块中不在当前作用域的变量。
>>> d = 1 >>> def func(a,b,c): ... print(a,b,c,d) ... >>> print(func.__code__.co_names) ('print', 'd') >>>
*co_varnames:一个元组,保存在当前作用域中的变量。
>>> d = 1 >>> def func(a,b,c): ... print(a,b,c,d) ... >>> print(func.__code__.co_varnames) ('a', 'b', 'c') >>>
*co_freevars:内层函数引用的外层函数的作用域中的变量,即块内使用,但是并未在该块内定义的变量(不含全局变量、内置变量)。
>>> def outer(): ... e = 'enclose' ... def inner(): ... print(e) ... return inner ... >>> print(outer().__code__.co_freevars) ('e',) >>>
*co_cellvars:是与co_freevars相对的概念,指被内部嵌套块(函数)引用的变量组成的元组。
>>> def outer(): ... e = 'enclose' ... def inner(): ... print(e) ... return inner ... >>> print(outer.__code__.co_cellvars) ('e',) >>
magic number
pyc文件主要包括三个部分,首先是magic number,其次是timestamp,然后是PyCodeObject。magic number 是Python定义的一个整数值,不同版本的Python会定义不同的magic number,这个值是为了保证Python能够加载正确的pyc。
typedef struct { unsigned short min; unsigned short max; wchar_t version[MAX_VERSION_SIZE]; } PYC_MAGIC; static PYC_MAGIC magic_values[] = { { 50823, 50823, L"2.0" }, { 60202, 60202, L"2.1" }, { 60717, 60717, L"2.2" }, { 62011, 62021, L"2.3" }, { 62041, 62061, L"2.4" }, { 62071, 62131, L"2.5" }, { 62151, 62161, L"2.6" }, { 62171, 62211, L"2.7" }, { 3000, 3131, L"3.0" }, { 3141, 3151, L"3.1" }, { 3160, 3180, L"3.2" }, { 3190, 3230, L"3.3" }, { 3250, 3310, L"3.4" }, { 3320, 3351, L"3.5" }, { 3360, 3379, L"3.6" }, { 3390, 3399, L"3.7" }, { 3400, 3419, L"3.8" }, { 3420, 3429, L"3.9" }, { 3430, 3439, L"3.10" }, { 0 } };
pyc文件
magic number 和 timestamp 的写入
根据调用关系,最终python调用此函数写入整型数据。可以看出python是一个字节一个字节的向文件写入数据的。
typedef struct {
FILE *fp;
int error; /* see WFERR_* values */
int depth;
PyObject *str;
char *ptr;
const char *end;
char *buf;
_Py_hashtable_t *hashtable;
int version;
} WFILE;
#define w_byte(c, p) do { \
if ((p)->ptr != (p)->end || w_reserve((p), 1)) \
*(p)->ptr++ = (c); \
} while(0)
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);
}
WFILE:对一个文件句柄进行了包装。
- PyObject *str:在写入文件时为一个dict对象,在读取文件时为一个list对象(为了处理intern的字符串)
w_object写入
根据调用关系,对PyCodeObject的写入最终调用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_complex_object,内部也是不断判断对象类型,然后写入,最终都调用的是w_byte,如果是列表、元组之类的数据,则会通过循环,递归w_object函数写入数据,例如PyDict对象,对遍历出来的每一个键,值都执行w_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)) { ... } else if (PyComplex_CheckExact(v)) { ... } else if (PyBytes_CheckExact(v)) { ... } else if (PyUnicode_CheckExact(v)) { ... } else if (PyTuple_CheckExact(v)) { ... } else if (PyList_CheckExact(v)) { ... } else if (PyDict_CheckExact(v)) { Py_ssize_t pos; PyObject *key, *value; W_TYPE(TYPE_DICT, p); /* This one is NULL object terminated! */ pos = 0; while (PyDict_Next(v, &pos, &key, &value)) { w_object(key, p); w_object(value, p); } w_object((PyObject *)NULL, p); } else if (PyAnySet_CheckExact(v)) { ... } else if (PyCode_Check(v)) { PyCodeObject *co = (PyCodeObject *)v; W_TYPE(TYPE_CODE, p); w_long(co->co_argcount, p); w_long(co->co_posonlyargcount, p); w_long(co->co_kwonlyargcount, p); w_long(co->co_nlocals, p); w_long(co->co_stacksize, p); w_long(co->co_flags, p); w_object(co->co_code, p); w_object(co->co_consts, p); w_object(co->co_names, p); w_object(co->co_varnames, p); w_object(co->co_freevars, p); w_object(co->co_cellvars, p); w_object(co->co_filename, p); w_object(co->co_name, p); w_long(co->co_firstlineno, p); w_object(co->co_linetable, p); } else if (PyObject_CheckBuffer(v)) { ... } else { W_TYPE(TYPE_UNKNOWN, p); p->error = WFERR_UNMARSHALLABLE; } }
- PyCode_Check(v),可以看到如果对象是一个PyCodeObject,则就直接写入,也就是说,如果对象包括很多代码块,那么在最大的那个代码块中一定包含了其余小的代码块,在写pyc文件时,小的代码块将被递归写入文件,也就是说,pyc文件是一个有组织结构的文件。
注意到,TYPE_CODE等类型信息,如果没有类型信息,那么当python再次加载pyc文件的时候,就没办法知道字节流中隐藏的结构和蕴含的信息,所以Python必须往pyc文件写入一个标识。如果Python在pyc中发现了这样的标识,则预示着上一个对象结束,新的对象开始,并且也知道新对象是什么样的对象,从而也知道该执行什么样的加载动作。
#define TYPE_NULL '0' #define TYPE_NONE 'N' #define TYPE_FALSE 'F' #define TYPE_TRUE 'T' #define TYPE_STOPITER 'S' #define TYPE_ELLIPSIS '.' #define TYPE_INT 'i' /* TYPE_INT64 is not generated anymore. Supported for backward compatibility only. */ #define TYPE_INT64 'I' #define TYPE_FLOAT 'f' #define TYPE_BINARY_FLOAT 'g' #define TYPE_COMPLEX 'x' #define TYPE_BINARY_COMPLEX 'y' #define TYPE_LONG 'l' #define TYPE_STRING 's' #define TYPE_INTERNED 't' #define TYPE_REF 'r' #define TYPE_TUPLE '(' #define TYPE_LIST '[' #define TYPE_DICT '{' #define TYPE_CODE 'c' #define TYPE_UNICODE 'u' #define TYPE_UNKNOWN '?' #define TYPE_SET '<' #define TYPE_FROZENSET '>' #define FLAG_REF '\x80' /* with a type, add obj to index */ #define TYPE_ASCII 'a' #define TYPE_ASCII_INTERNED 'A' #define TYPE_SMALL_TUPLE ')' #define TYPE_SHORT_ASCII 'z' #define TYPE_SHORT_ASCII_INTERNED 'Z'
Comments | NOTHING