• Python之code对象与pyc文件(二)


    上一节:Python之code对象与pyc文件(一)

    创建pyc文件的具体过程

    前面我们提到,Python在通过import或from xxx import xxx时会对module进行动态加载,如果没有找到相应的pyc或dll文件,就会在py文件的基础上创建pyc文件,之前说过,pyc文件中保存的是PyCodeObject对象,那么我们就要搞清楚,PyCodeObject是如何写入到pyc文件中的

    import.c

    static void
    write_compiled_module(PyCodeObject *co, char *cpathname, time_t mtime)
    {
    	FILE *fp;
    	//排他性打开文件
    	fp = open_exclusive(cpathname);
    	//<1>写入Python的magic number
    	PyMarshal_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION);
    	//<2>写入PyCodeObject对象
    	PyMarshal_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION);
    	//<3>写入时间信息
    	PyMarshal_WriteLongToFile((long)mtime, fp, Py_MARSHAL_VERSION);
    	fflush(fp);
    	fclose(fp);
    }
    

      

    write_compiled_module中的代码略有缩减,我们只保留最需要关注的部分。可以发现,一个pyc文件中实际上包含了3个部分独立的信息,Python中的magic number、PyCodeObject对象以及创建pyc文件的时间

    在<1>处,Python会将pyc_magic这个值写入到文件的开头,pyc_magic是一个整数值,不同版本的Python的都会定义不同的magic number,在Python加载一个pyc文件时,会先检查pyc文件中的pyc_magic与当前Python版本所对应的pyc_magic是否一致,避免了Python2.5加载Python1.5编译出来的pyc文件。之所以要做这个检查,是因为不同版本的Python的字节码指令都可能有做不同的变动,一些旧的指令会被新的指令所代替,甚至还会加入新的指令,这都是导致Python不兼容的问题

    在import.c中,可以在源代码的注释里找到Python1.5到Python2.5所有版本的magic number,我们可以看一下Python2.5定义的magic number:

    import.c

    #define MAGIC (62131 | ((long)'
    '<<16) | ((long)'
    '<<24))
    static long pyc_magic = MAGIC;
    

      

    在pyc中,在<3>处完成了向pyc文件写入时间信息的动作。在pyc文件中包含时间信息可以使Python对比pyc和最新的py文件进行对比,如果发现pyc的生成时间早于py文件的修改时间,则代表py文件被修改过,会重新编译pyc文件

    在上面代码的<2>处,Python会调用PyMarshal_WriteObjectToFile方法,将内存中的PyCodeObject对象写入pyc文件中,在write_compiled_module中,向pyc文件写入数据的动作最后会集中到下面所示的几个函数中

     现在,我们来看一下PyMarshal_WriteObjectToFile这个方法 

    marshal.c 

    void PyMarshal_WriteObjectToFile(PyObject *x, FILE *fp, int version)
    {
    	WFILE wf;
    	wf.fp = fp;
    	wf.error = 0;
    	wf.depth = 0;
    	wf.strings = (version > 0) ? PyDict_New() : NULL;
    	wf.version = version;
    	w_object(x, &wf);
    	Py_XDECREF(wf.strings);
    }
    

      

    PyMarshal_WriteObjectToFile这个方法中调用w_object这个方法,将对象真正写入到文件中

    marshal.c

    static void w_object(PyObject *v, WFILE *p)
    {
    	……
    	else if (PyTuple_Check(v)) {
    		……
    	}
    	else if (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  	'>'
    

      

  • 相关阅读:
    DLink无线路由器做交换机配置
    解决超过两小时的问题记录
    SIP学习之旅【资料收集篇】

    从google code里面获取代码的方法
    NSString表示的时间转为time_t
    C语言中 时间日期格式化符号 详解
    (转)time_t的定义
    (分享)简单圆角UITextView
    viewDidUnload释疑
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/9380778.html
Copyright © 2020-2023  润新知