温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Python编译结果之如何理解code对象与pyc文件

发布时间:2021-10-19 17:07:27 来源:亿速云 阅读:167 作者:iii 栏目:开发技术

本篇内容介绍了“Python编译结果之如何理解code对象与pyc文件”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

1、Python程序执行过程

与java类似,Python将.py编译为字节码,然后通过虚拟机执行。编译过程与虚拟机执行过程均在python25.dll中。Python虚拟机比java更抽象,离底层更远。

编译过程不仅生成字节码,还要包含常量、变量、占用栈的空间等,Pyton中编译过程生成code对象PyCodeObject。将PyCodeObject写入二进制文件,即.pyc。

Python编译结果之如何理解code对象与pyc文件

有必要则写入A.pyc指的是该.py是否只运行一次,如果import的模块,肯定会生成.pyc。

2、PyCodeObject对象与.pyc文件

Python解释器将.py程序编译为PyCodeObject对象,具体过程与编译原理类似。

typedef struct {     PyObject_HEAD     int co_argcount;	// Code Block的参数的个数,比如说一个函数的参数     int co_nlocals;	// Code Block中局部变量的个数     int co_stacksize;	// 执行该段Code Block需要的栈空间     int co_flags;	// N/A     PyObject *co_code;	// Code Block编译所得的byte code,以PyStringObject的形式存在     PyObject *co_consts;	// PyTupleObject对象,保存Code Block中的常量     PyObject *co_names;	// PyTupleObject对象,保存Code Block中的所有符号     PyObject *co_varnames;	// Code Block中局部变量名集合     PyObject *co_freevars;	// 实现闭包所需东西     PyObject *co_cellvars;  // Code Block内部嵌套函数所引用的局部变量名集合     PyObject *co_filename;	// Code Block所对应的.py文件的完整路径     PyObject *co_name;	// Code Block的名字,通常是函数名或类名     int co_firstlineno;	// Code Block在对应的.py文件中的起始行     PyObject *co_lnotab;	// byte code与.py文件中source code行号的对应关系,以PyStringObject的形式存在     void *co_zombieframe;     PyObject *co_weakreflist; } PyCodeObject;

一个Code Block生成一个PyCodeObject,进入一个名字空间成为进入一个Code Block。如下.py文件编译完成后会生成三个PyCodeObject,一个对应整个.py文件一个对应Class A,一个对应def Fun。实际这三个code对象是嵌套的,后两个code对象位于第一个code对象的co_consts属性中。其实,字节码位于co_code中。

class A:	pass def Fun():	pass a = A() Fun()

pyc文件包括三部分:

(1)四字节的Magic int,表示pyc版本信息

(2)四字节的int,是pyc产生时间,若与py文件时间不同会重新生成

(3)序列化了的PyCodeObject对象。

3、pyc文件的生成

写入pyc文件的函数包括以下几个步骤:

PyMarshal_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION);	// 写入版本信息 PyMarshal_WriteLongToFile(0L, fp, Py_MARSHAL_VERSION);	// 写入时间信息 PyMarshal_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION);	// 写入PyCodeObject对象

关键在于code对象的写入:

{     WFILE wf;     wf.fp = fp;	……     w_object(x, &wf); }

用到了一个WFILE结构体,可以认为是对FILE *fp 的一个封装:

typedef struct {     FILE *fp;     int error;     int depth;     PyObject *strings; // 存储字符串,写入时以dict形式,读出时以list形式 } WFILE;

关键在于w_object()函数:

static void w_object(PyObject *v, WFILE *p){	if (v == NULL)	……	else if (PyInt_CheckExact(v)) ……	else if (PyFloat_CheckExact(v)) ……	else if (PyString_CheckExact(v)) ……	else if (PyList_CheckExact(v)) …… }

w_code实质为根据不同的对象类型选取不同的策略,例如tuple对象:

else if (PyTuple_CheckExact(v)) {         w_byte(TYPE_TUPLE, p);         n = PyTuple_Size(v);         W_SIZE(n, p);         for (i = 0; i < n; i++)              w_object(PyTuple_GET_ITEM(v, i), p);

而所有类型最终可分解为写入数值与写入字符串两种操作,涉及以下几部分:

#define w_byte(c, p) putc((c), (p)->fp)	// 用于写入类型 static void w_long(long x, WFILE *p){	// 用于写入数字     w_byte((char)( x      & 0xff), p);	// 实质为用四个字节存储一个数字     w_byte((char)((x>> 8) & 0xff), p);     w_byte((char)((x>>16) & 0xff), p);     w_byte((char)((x>>24) & 0xff), p); } static void w_string(char *s, int n, WFILE *p){	//用于写入字符串     fwrite(s, 1, n, p->fp); }

由于序列化写入文件后丢失了结构信息,故写入每个对象时写入类型信息w_byte:

#define TYPE_INT                'i' #define TYPE_LIST               '[' #define TYPE_DICT               '{' #define TYPE_CODE               'c'

由于Python皆对象,w_object(PyObject*)便可针对不同类型选取不同写入方法,不断细分,最终分解为PyInt_Object或PyString_Object,利用w_long或w_string写入。

数字比较简单:

else if (PyInt_CheckExact(v)) {     w_byte(TYPE_INT, p);     w_long(x, p); }

字符串则比较复杂:

 else if (PyString_CheckExact(v)) {         if (p->strings && PyString_CHECK_INTERNED(v)) {             PyObject *o = PyDict_GetItem(p->strings, v);	// 获取在strings中的序号             if (o) {	// inter对象的非首次写入                 long w = PyInt_AsLong(o);                 w_byte(TYPE_STRINGREF, p);                 w_long(w, p);                 goto exit;             }             else {	// intern对象的首次写入                 int ok;                 ok = o && PyDict_SetItem(p->strings, v, o) >= 0;                 Py_XDECREF(o);                 w_byte(TYPE_INTERNED, p);             }         }         else {	// 写入普通string             w_byte(TYPE_STRING, p);         }         n = PyString_GET_SIZE(v);         W_SIZE(n, p);         w_string(PyString_AS_STRING(v), n, p);     }

(1)若写入普通字符串,写入字符串类型信息"S",然后写入字符串长度及string值。

(2)若写入inter字符串,先到WFILE的strings中查找:

(a)若找到,则写入引用类型信息"R",然后写入序号

(b)若未找到,创建对象放入strings,并写入intern类型信息"t",然后写入字符串长度及string值。
若依次写入"efei"、"snow"、"efei",则会如下:

Python编译结果之如何理解code对象与pyc文件

从pyc文件读入时,依靠list,那么序号就可以利用上了。

“Python编译结果之如何理解code对象与pyc文件”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI