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

Python程序的执行过程

我们都知道,C语言在执行之前需要将源代码编译成可执行的二进制文件,也就是将源代码翻译成机器代码,这种二进制文件一旦生成,即可用于执行。但是,Python是否一样呢?或许很多人都听过,Python和Java都是半编译半解释的语言,那么问题来了,什么又是半编译半解释呢?这还要从C语言开始说起

比方我们现在有一段C语言写成的程序,我们在一台Linux服务器上编译好了,生成可执行的二进制文件,可是我现在想要在一台Windows的机器上执行这个文件,这是不可能的,原因是因为不同平台间的机器代码是不一样的,在Linux机器上生成的二进制可执行文件,是不能拿到Windows上执行的,甚至都是在Linux上编译的文件,但是用的C编译器不同,一样有可能无法执行。所以,这才有了半编译半解释。

半编译半解释保证,一次编译,到处运行。这是Java的承诺,同样适用于Python。半编译半解释会从源代码中产生一组字节码,它并不是机器代码,但是不管是在Linux还是Windows的机器上,同样的源代码产生的字节码都是一样的,同时它还有个虚拟机,虚拟机会一条一条执行字节码,生成可执行的机器代码交给CPU执行。正是因为字节码和虚拟机这两个特性,使得我们的程序可以正常执行在Linux或Windows机器上

那么你一定好奇,Python的编译器和Python的虚拟机在什么地方呢?于是,我们来到安装Python的目录下,首先我们看到的是python.exe,于是我们怀疑python.exe是不是编译器或者虚拟机其中一个,不过我们发现,python.exe只有92KB,似乎并不大,不太可能支撑起一个庞大的语言。

实际上,Python编译器和Python虚拟机都在同一个文件里,而且还在上面最大的文件里,没错,就是python25.dll这个文件,这个文件既要完成编译工作,同时还要完成解释工作(即虚拟机的工作)

熟悉Java的同学都知道(不熟悉也没关系,举个栗子而已),Java在编译程序的时候,会产生一个class文件,最后调用Java命令执行class文件中的字节码。而Python作为同样的半编译半解释的语言,也有类似的特性,Python执行的时候,有可能会产生一个字节码文件,注意,只是有可能,而Java是一定会产生字节码文件。Python既可以直接执行源代码文件,也可以执行字节码文件,何时会产生这个字节码文件,我们且往后看

PyCodeObject对象与pyc文件

刚刚我们说,Python在执行一个文件时,是有可能产生字节码文件的,那么是在何时才会产生呢?我们可以写一个demo.py,里面随便你写什么程序,然后直接用python执行,会发现,不管我们怎么执行,都不会生成pyc文件。嗯……看来是我们的操作有点问题?那什么才是生成pyc文件的正确操作呢?

当然是当我们执行一个脚本时,脚本引入的模块会产生pyc文件了!

这样说可能还有些人不懂,没关系,我们直接上代码

# ls
demo.py
# cat demo.py
class A:
    pass

def func():
    pass

a = A()
func()

  

从上面的代码我们可以看到,在当前目录下只有一个demo.py的文件,并且我们打印出这个文件的内容,当然,这个内容不重要,怎么生成pyc文件最重要。我们再来看下面的代码,我们在当前的目录直接进入python命令行,然后引入demo模块再退出

# python
…………
>>> from demo import *
>>>
# ls
demo.py  demo.pyc

  

然后我们再打印当前目录下的文件,神奇的事发生了,多了一个demo.pyc文件。

为什么当引入一个Python文件时,这个Python文件对应的pyc文件会生成呢?可以这样认为,当一个文件被引入时,代表这个文件很可能是要经常被引用的,因此Python编译这个文件,生成字节码。而仅仅是执行一个脚本,Python会认为这个脚本只执行一次,后续不会再执行,所以,不会为其生成pyc文件

pyc文件中,存储着一个PyCodeObject对象,对于Python编译器来说,PyCodeObject对象才是真正的编译结果,而pyc文件只是这个对象在磁盘上的表现形式,它们其实是Python对源文件编译的结果的两种不同的存在方式。程序运行期间,编译结果存在于内存中的PyCodeObject对象中,而程序结束后,编译结果又被保存到pyc文件中,当下一次运行相同程序时,Python会根据pyc文件中记录的编译结果,直接在内存中建立PyCodeObject对象,而不用再次编译

PyCodeObject对象

我们先来看Python源码中关于PyCodeObject的声明:

/* 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 addr<->lineno mapping) */
    void *co_zombieframe;     /* for optimization only (see frameobject.c) */
} PyCodeObject;

    

PyCodeObject对象中的各个域所包含的信息我们会在下面一步一步挖掘开来。Python编译器在对Python源代码进行编译的时候,对于代码中的一个Code Block,会创建一个PyCodeObject对象与这段代码对应,那么,在代码中,怎么样才算是一个Code Block呢?Python中是这样定义的:当进入一个新的名字空间,或者说作用域时,我们算是进入一个新的Code Block了。

回顾一下上面的demo.py文件,Python编译器对源代码完成编译后,总共会创建3个PyCodeObject对象,一个是对应demo.py整个文件的,一个是对应class A所代表的Code Block,最后一个是def func所代表的Code Block

这里,我们提及Python中一个至关重要的概念——名字空间,名字空间是符号的上下文环境,符号的含义取决于名字空间。更具体的说,一个变量名对应的变量值是什么,在Python中是不确定的,而是通过名字空间来决定的

对于某个变量名,比如a,在同一个脚本中,可能在某一个类里,它代表的是一串字符串,而在另外一个方法中,它代表的是一个整型值,但a这个变量到底是什么值,就是由名字空间来决定的。名字空间可以一个套一个地形成一条名字空间链,Python虚拟机在执行的过程中,会有很大一部分时间消耗在从这条名字空间链中确定一个符号所对应的对象是什么

正如我们前面所说,一个Code Block就对应一个名字空间,即对应一个PyCodeObject对象。在Python中,类、函数、module都对应着一个独立的名字空间。因此,都会有一个PyCodeObject对象与其对应。所以,demo.py经过Python编译器编译后,一共得到3个PyCodeObject对象

pyc文件

每一个PyCodeObject对象都包含了每一个Code Block中所有Python源代码经过编译后得到的byte code序列,Python会将这些字节码序列和PyCodeObject对象一起存储在pyc文件中。要了解pyc文件,首先我们必须清楚PyCodeObject中大部分域所代表的含义,这一点是无论如何都不能绕过去的

PyCodeObject中域含义
Field Content
co_argcount Code Block的位置参数个数,比如说一个函数的位置参数个数
co_nlocals Code Block中局部变量的个数,包括其位置参数的个数
co_stacksize  执行该段Code Block需要的栈空间
co_flags  N/A,表示该域对理解Python虚拟机的行为没太多用处
co_code  Code Block编译所得的字节码指令序列,以PyStringObject的形式存在
co_consts  PyTuppleObject对象,保存Code Block中所有的常量
co_names  PyTuppleObject对象,保存Code Block中所有的符号
co_varnames  Code Block中的局部变量名集合
co_freevars  Python实现闭包需要用到的东西,为自由变量
co_cellvars  Code Block中内部嵌套函数所引用的局部变量名集合
co_filename  Code Block所对应的.py文件的完整路径
co_name  Code Block的名字,通常是函数名或类名
co_firstlineno  Code Block在对应的.py文件中的起始行
co_lnotab  字节码指令与.py文件中的source code行号的对应关系,以PyStringObject的形式存在

co_lnotab中的字节码和相应的source code行号的对应信息是以unsigned bytes的数组形式存在的,数组的形式可以看作(字节码指令在co_code中位置,source code行号)形式的一个list,如下面的表格:

表1-1
字节码在co_code中的偏移 .py文件中源代码的行号
0 1
6 2
50 7

这里有个小技巧,Python不会直接记录这些信息,但是它会记录这些信息间的增量值。所以,对应的co_lnotab应该如下表:

表1-2
字节码在co_code中的偏移 .py文件中源代码的行号
0 1
6(6+0=6) 1(1+1=2)
44(44+6+0=50) 5(5+1+1=7)

在Python中访问PyCodeObject对象

在Python中,有与C一级的PyCodeObject对象对应的对象——code对象,这个对象是对C一级的PyCodeObject对象的一个简单包装,通过code对象,我们可以访问到PyCodeObject对象中的各个域

>>> source = open("demo.py").read()
>>> co = compile(source, "demo.py", "exec")
>>> type(co)
<class ‘code‘>
>>> dir(co)
[‘__class__‘, ‘__delattr__‘, ‘__dir__‘, ‘__doc__‘, ‘__eq__‘, ‘__format__‘, ‘__ge__‘, ‘__getattribute__‘, ‘__gt__‘, ‘__hash__‘, ‘__init__‘, ‘__init_subclass__‘, ‘__le__‘, ‘__lt__‘, ‘__ne__‘, ‘__new__‘, ‘__reduce__‘, ‘__reduce_ex__‘, ‘__repr__‘, ‘__setattr__‘, ‘__sizeof__‘, ‘__str__‘, ‘__subclasshook__‘, ‘co_argcount‘, ‘co_cellvars‘, ‘co_code‘, ‘co_consts‘, ‘co_filename‘, ‘co_firstlineno‘, ‘co_flags‘, ‘co_freevars‘, ‘co_kwonlyargcount‘, ‘co_lnotab‘, ‘co_name‘, ‘co_names‘, ‘co_nlocals‘, ‘co_stacksize‘, ‘co_varnames‘]
>>> co.co_names
(‘A‘, ‘func‘, ‘a‘)
>>> co.co_name
‘<module>‘
>>> co.co_filename
‘demo.py‘

  

原文地址:https://www.cnblogs.com/beiluowuzheng/p/9379199.html

时间: 2024-09-28 02:01:12

Python之code对象与pyc文件(一)的相关文章

Python之code对象与pyc文件(三)

上一节:Python之code对象与pyc文件(二) 向pyc写入字符串 在了解Python如何将字符串写入到pyc文件的机制之前,我们先来了解一下结构体WFILE: marshal.c typedef struct { FILE *fp; int error; int depth; /* If fp == NULL, the following are valid: */ PyObject *str; char *ptr; char *end; PyObject *strings; /* di

关于python包,模块,.pyc文件和文件导入理解

参考文献 一.包 包是一个文件夹,用来存放模块和子包. 包里一般会有一个__init__.py的文件(也可以没有). 包里会有一个__pycache__文件夹,存放.py文件经解释器解释后的中间字节码(二进制文件). 二.模块 可以作为模块的文件有.py..pyc..pyo..pyd..so..dll文件. 三..pyc文件与.pyo文件 这两个文件都是二进制文件,由python解释器将.py文件转化成的二进制文件,目的是加快解释速度且可以隐藏源代码. python解释器在解释.py文件时,会优

Python编程时.py与.pyc文件的介绍

Python的程序中,是把原始程序代码放在.py文件里,而Python会在执行.py文件的时候.将.py形式的程序编译成中间式文件(byte-compiled)的.pyc文件,这么做的目的就是为了加快下次执行文件的速度. 所以,在我们运行python文件的时候,就会自动首先查看是否具有.pyc文件,如果有的话,而且.py文件的修改时间和.pyc的修改时间一样,就会读取.pyc文件,否则,Python就会读原来的.py文件. 其实并不是所有的.py文件在与运行的时候都会差生.pyc文件,只有在im

Python学习之pyc文件与code对象

本文和大家分享的主要是python中的pyc文件与code对象相关内容,一起来看看吧,希望对大家学习python有所帮助. python对源程序编译结果是生成一个 .pyc 文件. python对 .py 文件的编译结果是字节码, 为了能复用而不需要重新编译才有了写成 .pyc 文件. 对于解释器来说 PyCodeObject 对象才是真正编译结果, pyc文件只是这个对象在硬盘上的表现形式. PyCodeObject [code.h]typedef struct { PyObject_HEAD

compileall 编译源文件为pyc文件

有的时候我们需要把项目中.py的python所有源文件编译成.pyc文件,只保留.pyc文件然后发布给别人(虽然说可以反编译,但也算是一种保护把). 这个时候就可以使用compileall 库来完成这个工作,它可以递归的把一个文件夹下的所有.py文件编译成.pyc文件. 例如我有一个django项目在test文件夹下就可以 E:\>python -c "import compileall; import re; compileall.compile_dir('test', rx=re.co

删除项目开发中的.pyc文件

在实际开发中python会自动生成很多pyc文件,但是这些pyc文件是不需要我们追踪的,删除了对项目也没有影响,下面是删除pyc文件的方法. Linux或Mac系统 find /tmp -name "*.pyc" | xargs rm -rf 如果提示Permission denied sudo find /tmp -name "*.pyc" | xargs rm -rf 注意:将/tmp换成自己的工作目录 原文地址:https://www.cnblogs.com/

二、如何解决:python:Can&#39;t reopen .pyc file

如何解决:python:Can't reopen .pyc file pyc文件是python在编译过程中出现的主要中间过程文件.pyc文件以二进制形式存在,可以由python虚拟机直接执行的程序.通过生成.pyc文件可以提高程序运行的速度,编译过的.pyc程序代码也相当于转换了源代码,提高源码加密性.当然了,反编译还是存在的,但是还是可以一定程度上对程序进行包装,反编译程序也费时费力,工作量大. 我们可以使用如下格式运行*.py文件来生成*.pyc文件(*为文件名:path文根目录路径): p

Python生成pyc文件

pyc文件是py文件编译后生成的字节码文件(byte code).pyc文件经过Python解释器最终会生成机器码运行 为什么要手动提前生成pyc文件呢,主要是不想把源代码暴露出来. python -m foo.py    #生成单个pyc文件

如果解释Python,什么是.pyc文件?

我已经了解Python是一种解释型语言......但是,当我查看我的Python源代码时,我看到.pyc文件,Windows将其识别为“编译的Python文件”. 这些来自哪里? #1楼 Python代码经历了两个阶段. 第一步将代码编译成.pyc文件,这实际上是一个字节码. 然后使用CPython解释器解释此.pyc文件(字节码). 请参阅此链接. 这里用简单的术语解释代码编译和执行的过程. #2楼 它们包含字节代码 ,这是Python解释器编译源的代码. 然后,此代码由Python的虚拟机执