创建pyc文件的具体步骤
如上所述,Python在通过import或from xxx import xxx时动态加载模块,如果找不到相应的pyc或dll文件,则基于py文件创建pyc文件。 如上所述,pyc文件包含PyCodeObject对象
import.c
静态语音识别
write _ compiled _ module (pycodeobject * co,char *cpathname,time_t mtime ) )。
{
FILE *fp
//独占打开文件
FP=open_exclusive(cpathname;
//1写入python的幻灯片编号
py marshal _ writelongtofile (pyc _ magic,fp,Py_MARSHAL_VERSION );
//2写入pycodeobject对象
py marshal _ writeobject to file ((py object * ) co,fp,Py_MARSHAL_VERSION );
//3写入时间信息
py marshal _ writelongtofile ((long ) mtime,fp,Py_MARSHAL_VERSION );
闪存(FP;
流量(FP );
}
write_compiled_module的代码稍微缩小了一些,只留下了最值得注意的部分。 请注意,pyc文件实际上包含三个单独的信息: Python幻灯片编号、PyCodeObject对象和pyc文件的创建时间
在1中,Python将名为pyc_magic的值写入文件的开头。 pyc_magic是整数值,根据版本定义不同的magic number。 将pyc文件加载到Python时,首先检查pyc文件中的pyc_magic和当前pyc,因为不同版本的Python字节码指令可能不同。 一些旧指令会被新指令取代或添加新指令,这是导致Python不兼容的问题
在import.c中,源代码注释中可以找到从Python1.5到Python2.5的所有版本的幻灯片编号。 让我们看看Python2.5中定义的幻灯片编号:
import.c
#definemagic(62131|) (long )r'16 )|() long ) (n'24 ) )
静态long pyc _ magic=magic;
在pyc中,在3个位置向pyc文件写入时刻信息的动作完成了。 在pyc文件中包含时间信息后,Python会将pyc与最新的py文件进行比较,如果发现pyc的生成时间早于py文件的修改时间,则会修改py文件并重新编译pyc文件
在上面的代码的两个位置中,Python调用PyMarshal_WriteObjectToFile方法,将内存中的PyCodeObject对象写入pyc文件,然后将write _ compiled _ moded
现在,让我们来看看名为PyMarshal_WriteObjectToFile的方法
marshal.c
voidpymarshal _ writeobject to file (py object * x,FILE *fp,int version ) )。
{
WFILE wf;
wf.fp=fp;
wf.error=0;
wf.depth=0;
wf.strings=(版本0? pydict_new(:null;
wf.version=version;
w_object(x,wf );
py_xdecref(wf.strings;
}
在名为PyMarshal_WriteObjectToFile的方法中调用名为w_object的方法,将对象实际写入文件
marshal.c
staticvoidw_object(pyobject*v,WFILE *p ) )。
{
.
elseif(pytuple_check ) v ) }
.
}
elseif(pylist_check(v ) ) ) )
.
}
else if (PyDict_Check(v)) {
……
}
……
else if (PyCode_Check(v)) {
PyCodeObject *co = (PyCodeObject *)v;
w_byte(TYPE_CODE, p);
w_long(co->co_argcount, 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_lnotab, p);
}
……
}
从上面的代码我们可以看到,在w_object中,会遍历PyCodeObject中的各个域,将这些域一次写入。
当w_object面对一个PyListObject对象时,会有什么动作?
marshal.c
else if (PyList_Check(v)) {
w_byte(TYPE_LIST, p);
n = PyList_GET_SIZE(v);
w_long((long)n, p);
for (i = 0; i < n; i++) {
w_object(PyList_GET_ITEM(v, i), p);
}
}
如同前面对PyCodeObject一样,w_object还是遍历,将PyListObject对象中的每一个元素一次写入到pyc文件中
我们稍微浏览一遍w_object这个方法,会发现在写入任何一个对象之前,,都会先写入一个TYPE_LIST或者TYPE_CODE这样的类型标识,这些标识对于pyc文件再次加载具有至关重要的作用。如果我们仅仅是将对象中数值和字符串信息写入到pyc文件,如果没有对应的类型信息,我们很难将这些数值或者字符串恢复到以前在内存中所对应的对象。而在Python加载pyc文件时,发现了类型信息,就预示着上一个对象结束,新的对象开始,而且也知道新对象是什么类型的对象。这样,当Python加载pyc文件时,加载器才能知道在什么时候应该进行什么样的加载操作
类型标识在Python中的定义:
marshal.c
#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'
#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_STRINGREF'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 '>'