python 编译结果——Code对象与pyc文件
python源码
python源码在安装路径下可找到
- include: 包含了python所提供的所有头文件,都使用c编写
- lib: 包含了python自带的所有标准库,都使用python编写
- parse:包含python解释器中的Parse部分
ps:其他的不太清楚了,有知道的朋友还望留言使我补拙。
python程序执行过程
python虚拟机(解释器)先会对.py静态文件进行编译为字节码,存在内存的PyCodeObject对象中,当程序运行结束后,PyCodeObject对象中的内容会存在.pyc中。当程序下次运行的时候,python会从.pyc中记录的内容直接构建内存中的PyCodeObject对象,而不会再次编译。
换言之:PyCodeObject与.pyc是python程序字节码的不同表现形式。
上图中,python(版本).dll就是python的虚拟机(解释器)。(在python安装目录下可看到)
python源码中的PyCodeObject
[code.h]
/* Bytecode object */
typedef struct {
PyObject_HEAD
int co_argcount; /* #arguments, except *args */
int co_posonlyargcount; /* #positional only arguments */
int co_kwonlyargcount; /* #keyword only arguments */
int co_nlocals; /* #local variables */
int co_stacksize; /* #entries needed for evaluation stack */
int co_flags; /* CO_..., see below */
int co_firstlineno; /* first source line number */
PyObject *co_code; /* instruction opcodes */
PyObject *co_consts; /* list (constants used) */
PyObject *co_names; /* list of strings (names used) */
PyObject *co_varnames; /* tuple of strings (local variable names) */
PyObject *co_freevars; /* tuple of strings (free variable names) */
PyObject *co_cellvars; /* tuple of strings (cell variable names) */
/* The rest aren't used in either hash or comparisons, except for co_name,
used in both. This is done to preserve the name and line number
for tracebacks and debuggers; otherwise, constant de-duplication
would collapse identical functions/lambdas defined on different lines.
*/
Py_ssize_t *co_cell2arg; /* Maps cell vars which are arguments. */
PyObject *co_filename; /* unicode (where it was loaded from) */
PyObject *co_name; /* unicode (name, for reference) */
PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) See
Objects/lnotab_notes.txt for details. */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
PyObject *co_weakreflist; /* to support weakrefs to code objects */
/* Scratch space for extra data relating to the code object.
Type is a void* to keep the format private in codeobject.c to force
people to go through the proper APIs. */
void *co_extra;
/* Per opcodes just-in-time cache
*
* To reduce cache size, we use indirect mapping from opcode index to
* cache object:
* cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
*/
// co_opcache_map is indexed by (next_instr - first_instr).
// * 0 means there is no cache for this opcode.
// * n > 0 means there is cache in co_opcache[n-1].
unsigned char *co_opcache_map;
_PyOpcache *co_opcache;
int co_opcache_flag; // used to determine when create a cache.
unsigned char co_opcache_size; // length of co_opcache.
} PyCodeObject;
python编译器再对python编译的时候,对于代码中的一个Code Block会创建一个PyCodeObject对象与这段代码对应,那么如何确定多少代码算是一个Code Block呢?事实上,python有一个简单而清晰的规则:当进入一个新的名字空间,或者说作用域时,我们就算是进入了一个新的Code Block了。
比如对于下面这个demo,编译完之后总共会创建3个PyCodeObject对象,一个是对应demo.py整个文件的,一个是对应class A所代表的Code Block,而最后一个是对应 def func所代表的Code Block。
使用 python -m compileall 命令会编译当前目录中的所有.py文件。
pyc文件
每一个PyCodeObject对象中都包含了每一个Code Block中所有的python源代码经过编译后得到的byte code序列,前面有提到,python会将这些字节码序列和PyCodeObject对象一起存储在.pyc文件中,但是不幸的是,事实并不是总是这样。在命令行执行以下python demo.py会发现并没有产生一个对应的pyc文件。为什么呢?真实的原因不得而知,不过我们可以做出一个合理的猜测:有一些python程序只是临时完成一些琐碎的工作,比如统计某个特定文件中的词频信息,这样的程序可能仅仅运行一次,然后就再没用了,所以也就没有保存其对应的pyc文件的必要。因此,对于直接用pyhton demo.py这样形式执行的程序,python就不会存储编译结果了。不过可以用上边的命令手动编译。
但是加入说demo.py中实现的是一个需要被重用的类时。我们就希望能存储其对应的PyCodeObject对象,这样下次python就不会再次进行编译了。当在另一个程序,比如说在demo.py中对demo.py进行一个import demo的动态加载动作之后,你就会发现,python会为其产生pyc文件了。
这意味着python的import机制会触发pyc文件的生成。实际上,在python运行的过程中,如果碰到import abc 这样的语句,那么python将到设定好的path中寻找abc.pyc或者abc.dll文件,如果没有这些文件知识发现了abc.py,那么python会首先将abc.py编译成相应的PyCodeObject的中间结果,然后创建abc.pyc文件,并将中间结果写入该文件。接下来,python才会对abc.pyc文件进行import的动作,实际上就是将abc.pyc文件中的PyCodeObject重新在内存中复制出来。
在python中访问PyCodeObject对象
在python中,有与C 一级的PyCodeObject对象对应的对象————code对象。这个对象是对C 一级的PyCodeObject对象的一个简单包装,通过code对象,我们可以访问PyCodeObject对象中的各个域。
pyc文件的生成
内容太多了,看书吧。
字节码
python标准库提供了对python的code对象进行解析的工具dis。dis提供一个名为dis的方法,这个方法接受一个code对象,然后会输出code对象里的字节码指令信息。
- 最左边一列显示的是字节码指令对应源码中的行数,
- 左起第二列显示的是字节码指令再co_code中的偏移位置,
- 第三列显示了当前的字节码指令,
- 最后一列显示了当前字节码指令的参数。