Python的编译结果:pyc文件

从学编程开始老师就教我们Java是一门编译型语言。而《C#高级编程》中却没有说C#是一门编译型语言。我不知道编译型和解释型语言的明确区别,但是不论是Python还是Java、C#,他们的工作流程都一样,先编译撑一个中间语言,然后交给虚拟机/运行时/解释器来执行命令。Python的编译结果就是今天要讲的pyc文件。pyc文件经常让开发者忽略,而且十分讨厌。

Python的项目中总是带有一大堆的pyc文件,即使你删除了,你重新运行一下项目,他们还是会生成。但是很少有人知道什么时候会生成pyc。python a.py这样是不会生成a.pyc的,pyc文件的生成是通过import来触发的,也就是说,import a这样才会生成pyc文件。如果想知道pyc文件中存了什么,首先必须知道python的工作机制。
普通的python代码是不会运行的,必须编译成字节码,然后把字节码交给python解释器(python虚拟机),python虚拟机把这些字节码翻译成CPU指令。而pyc文件中的就是字节码和一些静态数据。

PyCodeObject对象就是Python编译器编译完py文件后在内存中的对象。PyCodeObject写入文件后就形成了pyc文件。所以PyCodeObject才是真正的编译结果。

typedef struct {
    PyObject_HEAD
    int co_argcount;        // arg的数量
    int co_nlocals;         // 局部变量的数量
    int co_stacksize;       // 需要用到的栈的空间
    int co_flags;           // 不知道干嘛用的
    PyObject *co_code;      // 字节码指令序列,以PyStringObject的形式存在
    PyObject *co_consts;    // PyTupleObject对象,保存这个PyCodeObject中的常量
    PyObject *co_names;     // PyTupleObject 变量名
    PyObject *co_varnames;  // 局部变量的变量名
    PyObject *co_freevars;  // 闭包
    PyObject *co_cellvars;  // 嵌套函数所引用的局部变量名集合
    PyObject *co_filename;  // 对应的文件的完整路径
    PyObject *co_name       // 命名空间的名字
    int co_firstlineno;     // 对应的源文件的这个PyCodeObject的第一行
    PyObject *co_lnotab;    // 字节码与行号的对应关系
    void *co_zombieframe;   //
} PyCodeObject;

PyCodeObject对应的是一个命名空间的代码块,所以一个py文件可以生成很多个PyCodeObject对象,这些PyCodeObject对象是以与代码相应的嵌套结构存放在一个pyc文件中。而pyc中存放的不仅仅是PyCodeObject对象,还存放了magic number和pyc文件的创建时间。magic number是用来确定python的版本的一个特殊数字,pyc创建时间是用来确定是否需要重新生成pyc文件。

PyCodeObject写入文件主要通过了两个基本的方法,w_long(longx, WFILE p)和w_string(char s, int n, WFILE p)方法,而python源码又将这些两个方法组合撑一个对外的方法w_object(PyObject v, WFILE *p)方法。在写入一个变量之前,总是会在前面先写入这个变量的类型,例如TYPE_INT,TYPE_LIST, TYPE_CODE等,这些类型标识以及标识对应的简写符号全部放在import.c文件中。Python在向pyc文件写入字符串的时候比较特殊,要注意interned机制(在pyc中也存在interned机制)。

查看PyCodeObject对象的方法。

>>> f = open("tt.py").read()
>>> co = compile(f, "tt.py", "exec")
>>> dir(co)
>>> co.co_code

另外也能查看一个CodeObjectBlock对应的字节码。


In [8]: import a
"hi"

In [9]: import dis     

In [10]: dis.dis(a)
Disassembly of say:
  5           0 LOAD_CONST               1 ('hello world')
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE