.pyc和PyCodeObject是什么?

转自:https://www.cnblogs.com/slydevil/p/7125419.html

一、.pyc是什么

1. python是一门解释型语言?


    初学python时,听到的关于python的第一句话就是,python是一门解释型语言,直到发现了*.pyc文件的存在。如果是解释型语言,那么生成的*.pyc文件时什么呢?

    c应该是compiled的缩写。


    为了防止学习python时被这句话误解,需要澄清这个问题,并且把一些基础概念给理清。

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

[cpp]  view plain  copy
  1. typedef struct {  
  2.     PyObject_HEAD  
  3.     int co_argcount;        // Code Block的参数的个数,比如说一个函数的参数  
  4.     int co_nlocals;         // Code Block中局部变量的个数  
  5.     int co_stacksize;       // 执行该段Code Block需要的栈空间  
  6.     int co_flags;           // N/A  
  7.     PyObject *co_code;      // Code Block编译所得的byte code,以PyStringObject的形式存在  
  8.     PyObject *co_consts;    // PyTupleObject对象,保存Code Block中的常量  
  9.     PyObject *co_names;     // PyTupleObject对象,保存Code Block中的所有符号  
  10.     PyObject *co_varnames;  // Code Block中局部变量名集合  
  11.     PyObject *co_freevars;  // 实现闭包所需东西  
  12.     PyObject *co_cellvars;  // Code Block内部嵌套函数所引用的局部变量名集合  
  13.     PyObject *co_filename;  // Code Block所对应的.py文件的完整路径  
  14.     PyObject *co_name;      // Code Block的名字,通常是函数名或类名  
  15.     int co_firstlineno;     // Code Block在对应的.py文件中的起始行  
  16.     PyObject *co_lnotab;    // byte code与.py文件中source code行号的对应关系,以PyStringObject的形式存在  
  17.     void *co_zombieframe;  
  18.     PyObject *co_weakreflist;  
  19. } PyCodeObject;  
一个Code Block生成一个PyCodeObject,进入一个名字空间成为进入一个Code Block。如下.py文件编译完成后会生成三个PyCodeObject,一个对应整个.py文件一个对应Class A,一个对应def Fun。实际这三个code对象是嵌套的,后两个code对象位于第一个code对象的co_consts属性中。其实,字节码位于co_code中。

[python]  view plain  copy
  1. class A:  
  2.     pass  
  3. def Fun():  
  4.     pass  
  5. a = A()  
  6. Fun()  
------------------------------------------------------------------
pyc文件包括三部分:
(1)四字节的Magic int,表示pyc版本信息
(2)四字节的int,是pyc产生时间,若与py文件时间不同会重新生成
(3)序列化了的PyCodeObject对象。

3、pyc文件的生成

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

[cpp]  view plain  copy
  1. PyMarshal_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION);   // 写入版本信息  
  2. PyMarshal_WriteLongToFile(0L, fp, Py_MARSHAL_VERSION);          // 写入时间信息  
  3. PyMarshal_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION);    // 写入PyCodeObject对象  
关键在于code对象的写入:

[cpp]  view plain  copy
  1. {  
  2.     WFILE wf;  
  3.     wf.fp = fp;  
  4.     ……  
  5.     w_object(x, &wf);  
  6. }  
用到了一个WFILE结构体,可以认为是对FILE *fp 的一个封装:

[cpp]  view plain  copy
  1. typedef struct {  
  2.     FILE *fp;  
  3.     int error;  
  4.     int depth;  
  5.     PyObject *strings; // 存储字符串,写入时以dict形式,读出时以list形式  
  6. } WFILE;  
关键在于w_object()函数:

[cpp]  view plain  copy
  1. static void w_object(PyObject *v, WFILE *p){  
  2.     if (v == NULL)  ……  
  3.     else if (PyInt_CheckExact(v)) ……  
  4.     else if (PyFloat_CheckExact(v)) ……  
  5.     else if (PyString_CheckExact(v)) ……  
  6.     else if (PyList_CheckExact(v)) ……  
  7. }  

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

[cpp]  view plain  copy
  1. else if (PyTuple_CheckExact(v)) {  
  2.     w_byte(TYPE_TUPLE, p);  
  3.     n = PyTuple_Size(v);  
  4.     W_SIZE(n, p);  
  5.     for (i = 0; i < n; i++)   
  6.         w_object(PyTuple_GET_ITEM(v, i), p);  
而所有类型最终可分解为写入数值与写入字符串两种操作,涉及以下几部分:

[cpp]  view plain  copy
  1. #define w_byte(c, p) putc((c), (p)->fp)  // 用于写入类型  
  2. static void w_long(long x, WFILE *p){   // 用于写入数字     
  3.     w_byte((char)( x      & 0xff), p);  // 实质为用四个字节存储一个数字  
  4.     w_byte((char)((x>> 8) & 0xff), p);  
  5.     w_byte((char)((x>>16) & 0xff), p);  
  6.     w_byte((char)((x>>24) & 0xff), p);  
  7. }  
  8. static void w_string(char *s, int n, WFILE *p){ //用于写入字符串  
  9.     fwrite(s, 1, n, p->fp);  
  10. }  
由于序列化写入文件后丢失了结构信息,故写入每个对象时写入类型信息w_byte:

[cpp]  view plain  copy
  1. #define TYPE_INT                'i'  
  2. #define TYPE_LIST               '['  
  3. #define TYPE_DICT               '{'  
  4. #define TYPE_CODE               'c'  

------------------------------------------------------------------------------------------------

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

数字 比较简单:

[cpp]  view plain  copy
  1. else if (PyInt_CheckExact(v)) {  
  2.     w_byte(TYPE_INT, p);  
  3.     w_long(x, p);  
  4. }  
字符串 则比较复杂:
[cpp]  view plain  copy
  1. else if (PyString_CheckExact(v)) {  
  2.        if (p->strings && PyString_CHECK_INTERNED(v)) {  
  3.            PyObject *o = PyDict_GetItem(p->strings, v);  // 获取在strings中的序号  
  4.            if (o) {         // inter对象的非首次写入  
  5.                long w = PyInt_AsLong(o);  
  6.                w_byte(TYPE_STRINGREF, p);  
  7.                w_long(w, p);  
  8.                goto exit;  
  9.            }  
  10.            else {               // intern对象的首次写入  
  11.                int ok;  
  12.                ok = o && PyDict_SetItem(p->strings, v, o) >= 0;  
  13.                Py_XDECREF(o);  
  14.                w_byte(TYPE_INTERNED, p);  
  15.            }  
  16.        }  
  17.        else {                   // 写入普通string  
  18.            w_byte(TYPE_STRING, p);  
  19.        }  
  20.        n = PyString_GET_SIZE(v);  
  21.        W_SIZE(n, p);  
  22.        w_string(PyString_AS_STRING(v), n, p);  
  23.    }          
(1)若写入普通字符串,写入字符串类型信息"S",然后写入字符串长度及string值。
(2)若写入inter字符串,先到WFILE的strings中查找:
        (a)若找到,则写入引用类型信息"R",然后写入序号
        (b)若未找到,创建对象放入strings,并写入intern类型信息"t",然后写入字符串长度及string值。
若依次写入"efei"、"snow"、"efei",则会如下:


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值