转自:https://www.cnblogs.com/slydevil/p/7125419.html
一、.pyc是什么
1. python是一门解释型语言?
初学python时,听到的关于python的第一句话就是,python是一门解释型语言,直到发现了*.pyc文件的存在。如果是解释型语言,那么生成的*.pyc文件时什么呢?
c应该是compiled的缩写。
2. 解释型语言和编译型语言
计算机是不能识别高级语言的,所以当我们运行一个高级语言程序的时候就需要一个“翻译机”来从事把高级语言转变成计算机能读懂的机器语言的过程。这个过程分成两类,第一种是编译,第二种是解释。
编译型语言在程序执行之前,先会通过编译器对程序执行一个编译的过程,把程序转变成机器语言。运行时就不需要翻译,而直接执行就可以了。最典型的例子就是C语言。
解释型语言就没有这个编译的过程,而是程序运行的时候,通过解释器对程序逐行做出解释,然后直接运行,最典型的例子就是Ruby。
通过以上的例子,总结一下解释型语言和编译型语言的优缺点。因为编译型语言在程序运行之前就已经对程序做出了“翻译”,所以在运行时就烧掉了“翻译”的过程,所以效率比较高。但是我们也不能一概而论,一些解释型语言也可以通过解释器的优化来在对程序做出翻译时对整个程序做出优化,从而在效率上接近编译型语言。
此外,随着Java等基于虚拟机的语言的兴起,我们有不能把语言纯粹地分成解释型和编译型这两种。
用Java来举例,java首先是通过编译器编译成字节码文件,然后在运行时通过解释器给解释成机器文件。所以说java是一种先编码后解释的语言。
3. python到底是什么
其实python和java/C#一样,也是一门基于虚拟机的语言。当我们在命令行中输入python hello.py时,其实是激活了python的“解释器”,告诉“解释器”:你要开始工作了。
可是在“解释”之前,其实执行的第一项工作和java一样,是编译。
python是一门先编译后解释的语言。
4. 简述python的运行过程
在说这个问题之前,先说两个概念,PyCodeObject和pyc文件。其实PyCodeObject则是python编译器真正编译成的结果
当python程序运行时,编译的结果则是保存在位于内存中的PyCodeObject中,当python程序运行结束时,python解释器则将PyCodeObject写回到pyc文件中。
当python程序第二次运行时,首先程序会在硬盘中寻找pyc文件,如果找到,则直接载入,否则就重复上面的过程。
所以我们应该这样来定位PyCodeObject和pyc文件,我们说pyc文件其实是PyCodeObject的一种持久化保存方式。
可视化:
Python程序执行过程
转自:https://blog.csdn.net/efeics/article/details/9255193
与java类似,Python将.py编译为字节码,然后通过虚拟机执行。编译过程与虚拟机执行过程均在python25.dll中。Python虚拟机比java更抽象,离底层更远。编译过程不仅生成字节码,还要包含常量、变量、占用栈的空间等,Pyton中编译过程生成code对象PyCodeObject。将PyCodeObject写入二进制文件,即.pyc。
有必要则写入A.pyc指的是该.py是否只运行一次,如果import的模块,肯定会生成.pyc。
二、不怎么能看懂:
PyCodeObject对象与.pyc文件
Python解释器将.py程序编译为PyCodeObject对象,具体过程与编译原理类似。
转自:https://blog.csdn.net/efeics/article/details/9255193
- 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;
- 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对象
- {
- WFILE wf;
- wf.fp = fp;
- ……
- w_object(x, &wf);
- }
- typedef struct {
- FILE *fp;
- int error;
- int depth;
- PyObject *strings; // 存储字符串,写入时以dict形式,读出时以list形式
- } WFILE;
- 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);
- }
- #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);
- }
(2)若写入inter字符串,先到WFILE的strings中查找:
(a)若找到,则写入引用类型信息"R",然后写入序号
(b)若未找到,创建对象放入strings,并写入intern类型信息"t",然后写入字符串长度及string值。
若依次写入"efei"、"snow"、"efei",则会如下:
从pyc文件读入时,依靠list,那么序号就可以利用上了。