compiled python file_py, pyc, pyw, pyo, pyd Compiled Python File (.pyc) 和Java或.NET相比,Python的Virtua...

Python在执行时会将.py文件编译成字节码并存储在PyCodeObject对象中,编译后的结果可能被保存在.pyc文件中以提高执行效率。本文深入探讨了PyCodeObject的结构、Python编译过程以及.pyc文件的生成和格式,揭示了Python如何通过.Pyc文件实现类似Java或.NET的字节码机制。内容包括PyCodeObject的域、.pyc文件的生成过程、字符串的写入机制以及Python字节码的结构和解析。
摘要由CSDN通过智能技术生成

1.      PyCodeObject与Pyc文件

通常认为,Python是一种解释性的语言,但是这种说法是不正确的,实际上,Python在执行时,首先会将.py文件中的源代码编译成Python的byte code(字节码),然后再由Python Virtual Machine来执行这些编译好的byte code。这种机制的基本思想跟Java,.NET是一致的。然而,Python Virtual Machine与Java或.NET的Virtual Machine不同的是,Python的Virtual Machine是一种更高级的Virtual Machine。这里的高级并不是通常意义上的高级,不是说Python的Virtual Machine比Java或.NET的功能更强大,更拽,而是说和Java或.NET相比,Python的Virtual Machine距离真实机器的距离更远。或者可以这么说,Python的Virtual Machine是一种抽象层次更高的Virtual Machine。

我们来考虑下面的Python代码:

[demo.py]

class A:

pass

def Fun():

pass

value = 1

str = “Python”

a = A()

Fun()

Python在执行CodeObject.py时,首先需要进行的动作就是对其进行编译,编译的结果是什么呢?当然有字节码,否则Python也就没办法在玩下去了。然而除了字节码之外,还包含其它一些结果,这些结果也是Python运行的时候所必需的。看一下我们的demo.py,用我们的眼睛来解析一下,从这个文件中,我们可以看到,其中包含了一些字符串,一些常量值,还有一些操作。当然,Python对操作的处理结果就是自己码。那么Python的编译过程对字符串和常量值的处理结果是什么呢?实际上,这些在Python源代码中包含的静态的信息都会被Python收集起来,编译的结果中包含了字符串,常量值,字节码等等在源代码中出现的一切有用的静态信息。而这些信息最终会被存储在Python运行期的一个对象中,当Python运行结束后,这些信息甚至还会被存储在一种文件中。这个对象和文件就是我们这章探索的重点:PyCodeObject对象和Pyc文件。

可以说,PyCodeObject就是Python源代码编译之后的关于程序的静态信息的集合:

[compile.h]

/* Bytecode object */

typedef struct {

PyObject_HEAD

int co_argcount;        /* #arguments, except *args */

int co_nlocals;     /* #local variables */

int co_stacksize;       /* #entries needed for evaluation stack */

int co_flags;       /* CO_..., see below */

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 doesn't count for hash/cmp */

PyObject *co_filename;  /* string (where it was loaded from) */

PyObject *co_name;      /* string (name, for reference) */

int co_firstlineno;     /* first source line number */

PyObject *co_lnotab;    /* string (encoding addrlineno mapping) */

} PyCodeObject;

在对Python源代码进行编译的时候,对于一段Code(Code Block),会创建一个PyCodeObject与这段Code对应。那么如何确定多少代码算是一个Code Block呢,事实上,当进入新的作用域时,就开始了新的一段Code。也就是说,对于下面的这一段Python源代码:

[CodeObject.py]

class A:

pass

def Fun():

pass

a = A()

Fun()

在Python编译完成后,一共会创建3个PyCodeObject对象,一个是对应CodeObject.py的,一个是对应class A这段Code(作用域),而最后一个是对应def Fun这段Code的。每一个PyCodeObject对象中都包含了每一个代码块经过编译后得到的byte code。但是不幸的是,Python在执行完这些byte code后,会销毁PyCodeObject,所以下次再次执行这个.py文件时,Python需要重新编译源代码,创建三个PyCodeObject,然后执行byte code。

很不爽,对不对?Python应该提供一种机制,保存编译的中间结果,即byte code,或者更准确地说,保存PyCodeObject。事实上,Python确实提供了这样一种机制——Pyc文件。

Python中的pyc文件正是保存PyCodeObject的关键所在,我们对Python解释器的分析就从pyc文件,从pyc文件的格式开始。

在分析pyc的文件格式之前,我们先来看看如何产生pyc文件。在执行一个.py文件中的源代码之后,Python并不会自动生成与该.py文件对应的.pyc文件。我们需要自己触发Python来创建pyc文件。下面我们提供一种使Python创建pyc文件的方法,其实很简单,就是利用Python的import机制。

在Python运行的过程中,如果碰到import abc,这样的语句,那么Python将到设定好的path中寻找abc.pyc或abc.dll文件,如果没有这些文件,而只是发现了abc.py,那么Python会首先将abc.py编译成相应的PyCodeObject的中间结果,然后创建abc.pyc文件,并将中间结果写入该文件。接下来,Python才会对abc.pyc文件进行一个import的动作,实际上也就是将abc.pyc文件中的PyCodeObject重新在内存中复制出来。了解了这个过程,我们很容易利用下面所示的generator.py来创建上面那段代码(CodeObjectt.py)对应的pyc文件了。

generator.py

CodeObject.py

import test

print "Done"

class A:

pass

def Fun():

pass

a = A()

Fun()

图1所示的是Python产生的pyc文件:

可以看到,pyc是一个二进制文件,那么Python如何解释这一堆看上去毫无意义的字节流就至关重要了。这也就是pyc文件的格式。

要了解pyc文件的格式,首先我们必须要清楚PyCodeObject中每一个域都表示什么含义,这一点是无论如何不能绕过去的。

Field

Content

co_argcount

Code Block的参数的个数,比如说一个函数的参数

co_nlocals

Code Block中局部变量的个数

co_stacksize

执行该段Code Block需要的栈空间

co_flags

N/A

co_code

Code Block编译所得的byte code。以PyStringObject的形式存在

co_consts

PyTupleObject对象,保存该Block中的常量

co_names

PyTupleObject对象,保存该Block中的所有符号

co_varnames

N/A

co_freevars

N/A

co_cellvars

N/A

co_filename

Code Block所对应的.py文件的完整路径

co_name

Code Block的名字,通常是函数名或类名

co_firstlineno

Code Block在对应的.py文件中的起始行

co_lnotab

byte code与.py文件中source code行号的对应关系,以PyStringObject的形式存在

需要说明一下的是co_lnotab域。在Python2.3以前,有一个byte code,唤做SET_LINENO,这个byte code会记录.py文件中source code的位置信息,这个信息对于调试和显示异常信息都有用。但是,从Python2.3之后,Python在编译时不会再产生这个byte code,相应的,Python在编译时,将这个信息记录到了co_lnotab中。

co_lnotab中的byte code和source code的对应信息是以unsigned bytes的数组形式存在的,数组的形式可以看作(byte code在co_code中位置增量,代码行数增量)形式的一个list。比如对于下面的例子:

Byte code在co_code中的偏移

.py文件中源代码的行数

0

1

6

2

50

7

这里有一个小小的技巧,Python不会直接记录这些信息,相反,它会记录这些信息间的增量值,所以,对应的co_lnotab就应该是:0,1, 6,1, 44,5。

2.      Pyc文件的生成

前面我们提到,Python在import时,如果没有找到相应的pyc文件或dll文件,就会在py文件的基础上自动创建pyc文件。那么,要想了解pyc的格式到底是什么样的,我们只需要考察Python在将编译得到的PyCodeObject写入到pyc文件中时到底进行了怎样的动作就可以了。下面的函数就是我们的切入点:

[import.c]

static void write_compiled_module(PyCodeObject *co, char *cpathname, long mtime)

{

FILE *fp;

fp = open_exclusive(cpathname);

PyMarshal_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION);

/* First write a 0 for mtime */

PyMarshal_WriteLongToFile(0L, fp, Py_MARSHAL_VERSION);

PyMarshal_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION);

/* Now write the true mtime */

fseek(fp, 4L, 0);

PyMarshal_WriteLongToFile(mtime, fp, Py_MARSHAL_VERSION);

fflush(fp);

fclose(fp);

}

这里的cpathname当然是pyc文件的绝对路径。首先我们看到会将pyc_magic这个值写入到文件的开头。实际上,pyc­_magic对应一个MAGIC的值。MAGIC是用来保证Py

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值