|
| 1 | +# Python 的编译结果——Code 对象与 pyc 文件 |
| 2 | + |
| 3 | +## Python 脚本的执行过程 |
| 4 | + |
| 5 | +- 编译 |
| 6 | + |
| 7 | + Python 编译器将 py 文件编译成字节码(PyCodeObject)。 |
| 8 | + |
| 9 | +- 执行 |
| 10 | + |
| 11 | + Python 虚拟机执行字节码。 |
| 12 | + |
| 13 | + |
| 14 | +## PyCodeObject |
| 15 | + |
| 16 | +PyCodeObject 定义: |
| 17 | + |
| 18 | +```C |
| 19 | +// Include/code.h |
| 20 | + |
| 21 | +typedef struct { |
| 22 | + PyObject_HEAD |
| 23 | + int co_argcount; /* #arguments, except *args */ |
| 24 | + int co_nlocals; /* #local variables */ |
| 25 | + int co_stacksize; /* #entries needed for evaluation stack */ |
| 26 | + int co_flags; /* CO_..., see below */ |
| 27 | + PyObject *co_code; /* instruction opcodes */ |
| 28 | + PyObject *co_consts; /* list (constants used) */ |
| 29 | + PyObject *co_names; /* list of strings (names used) */ |
| 30 | + PyObject *co_varnames; /* tuple of strings (local variable names) */ |
| 31 | + PyObject *co_freevars; /* tuple of strings (free variable names) */ |
| 32 | + PyObject *co_cellvars; /* tuple of strings (cell variable names) */ |
| 33 | + /* The rest doesn't count for hash/cmp */ |
| 34 | + PyObject *co_filename; /* string (where it was loaded from) */ |
| 35 | + PyObject *co_name; /* string (name, for reference) */ |
| 36 | + int co_firstlineno; /* first source line number */ |
| 37 | + PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) */ |
| 38 | + void *co_zombieframe; /* for optimization only (see frameobject.c) */ |
| 39 | +} PyCodeObject; |
| 40 | +``` |
| 41 | + |
| 42 | +Python 会对源代码中的每个代码块(Code Block)生成一个 PyCodeObject 对象,一个作用域(名字空间)就算做一个代码块。 |
| 43 | + |
| 44 | +代码块对应的字节码就存放在 PyCodeObject 的 `co_code`字段。 |
| 45 | + |
| 46 | +## 生成 pyc 文件 |
| 47 | + |
| 48 | +pyc文件包含三部分信息: |
| 49 | + |
| 50 | +- magic number |
| 51 | + |
| 52 | + 用于标识 pyc 版本,不同Python 版本的 magic number 不一样。通过 magic number 可以避免 Python 加载错误版本的 pyc 文件。 |
| 53 | + |
| 54 | + Python2.5 的 magic number 为: |
| 55 | + |
| 56 | + ```C |
| 57 | + #define MAGIC (62131 | ((long)'\r'<<16) | ((long)'\n'<<24)) |
| 58 | + |
| 59 | + /* Magic word as global; note that _PyImport_Init() can change the |
| 60 | + value of this global to accommodate for alterations of how the |
| 61 | + compiler works which are enabled by command line switches. */ |
| 62 | + static long pyc_magic = MAGIC; |
| 63 | + ``` |
| 64 | + |
| 65 | +- mtime |
| 66 | + |
| 67 | + 文件修改时间,用于和 py 文件的修改时间进行对比。Python 在导入模块时优先导入pyc 文件,但是有可能 pyc 文件对应的 py 文件已经被修改了,所以 Python 需要对比 pyc 文件和 py 文件的修改时间,如果 py 文件的修改时间更新,则需要重新编译 pyc 文件。 |
| 68 | + |
| 69 | +- 字节码,即 PyCodeObject。 |
| 70 | + |
| 71 | +生成 pyc 文件的代码如下: |
| 72 | + |
| 73 | +```C |
| 74 | +// Python/import.c |
| 75 | + |
| 76 | +/* Write a compiled module to a file, placing the time of last |
| 77 | + modification of its source into the header. |
| 78 | + Errors are ignored, if a write error occurs an attempt is made to |
| 79 | + remove the file. */ |
| 80 | + |
| 81 | +static void |
| 82 | +write_compiled_module(PyCodeObject *co, char *cpathname, time_t mtime) |
| 83 | +{ |
| 84 | + FILE *fp; |
| 85 | + |
| 86 | + fp = open_exclusive(cpathname); |
| 87 | + if (fp == NULL) { |
| 88 | + if (Py_VerboseFlag) |
| 89 | + PySys_WriteStderr( |
| 90 | + "# can't create %s\n", cpathname); |
| 91 | + return; |
| 92 | + } |
| 93 | + PyMarshal_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION); |
| 94 | + /* First write a 0 for mtime */ |
| 95 | + PyMarshal_WriteLongToFile(0L, fp, Py_MARSHAL_VERSION); |
| 96 | + PyMarshal_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION); |
| 97 | + if (fflush(fp) != 0 || ferror(fp)) { |
| 98 | + if (Py_VerboseFlag) |
| 99 | + PySys_WriteStderr("# can't write %s\n", cpathname); |
| 100 | + /* Don't keep partial file */ |
| 101 | + fclose(fp); |
| 102 | + (void) unlink(cpathname); |
| 103 | + return; |
| 104 | + } |
| 105 | + /* Now write the true mtime */ |
| 106 | + fseek(fp, 4L, 0); |
| 107 | + assert(mtime < LONG_MAX); |
| 108 | + PyMarshal_WriteLongToFile((long)mtime, fp, Py_MARSHAL_VERSION); |
| 109 | + fflush(fp); |
| 110 | + fclose(fp); |
| 111 | + if (Py_VerboseFlag) |
| 112 | + PySys_WriteStderr("# wrote %s\n", cpathname); |
| 113 | +} |
| 114 | +``` |
| 115 | +
|
| 116 | +`PyMarshal_WriteObjectToFile`最终调用 `w_object`将 PyCodeObject 写入文件中,`w_object`的代码如下(有删减): |
| 117 | +
|
| 118 | +```C |
| 119 | +// Python/marchal.c |
| 120 | +
|
| 121 | +// w_object 的代码很长,都是 if/else if 判断 object 的具体类型。 |
| 122 | +
|
| 123 | +static void |
| 124 | +w_object(PyObject *v, WFILE *p) |
| 125 | +{ |
| 126 | + ... |
| 127 | + else if (PyCode_Check(v)) { |
| 128 | + PyCodeObject *co = (PyCodeObject *)v; |
| 129 | + w_byte(TYPE_CODE, p); |
| 130 | + w_long(co->co_argcount, p); |
| 131 | + w_long(co->co_nlocals, p); |
| 132 | + w_long(co->co_stacksize, p); |
| 133 | + w_long(co->co_flags, p); |
| 134 | + w_object(co->co_code, p); |
| 135 | + w_object(co->co_consts, p); |
| 136 | + w_object(co->co_names, p); |
| 137 | + w_object(co->co_varnames, p); |
| 138 | + w_object(co->co_freevars, p); |
| 139 | + w_object(co->co_cellvars, p); |
| 140 | + w_object(co->co_filename, p); |
| 141 | + w_object(co->co_name, p); |
| 142 | + w_long(co->co_firstlineno, p); |
| 143 | + w_object(co->co_lnotab, p); |
| 144 | + } |
| 145 | + ... |
| 146 | +``` |
| 147 | + |
| 148 | +## dis |
| 149 | + |
| 150 | +Python 标准库中的 dis 模块可以用来获取代码对应的字节码。 |
| 151 | + |
0 commit comments