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的声明:
code.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 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中大部分域所代表的含义,这一点是无论如何都不能绕过去的
| 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'
Python之code对象与pyc文件(一)的更多相关文章
- Python之code对象与pyc文件(三)
		
上一节:Python之code对象与pyc文件(二) 向pyc写入字符串 在了解Python如何将字符串写入到pyc文件的机制之前,我们先来了解一下结构体WFILE: marshal.c typede ...
 - Python之code对象与pyc文件(二)
		
上一节:Python之code对象与pyc文件(一) 创建pyc文件的具体过程 前面我们提到,Python在通过import或from xxx import xxx时会对module进行动态加载,如果 ...
 - 《python解释器源码剖析》第8章--python的字节码与pyc文件
		
8.0 序 我们日常会写各种各样的python脚本,在运行的时候只需要输入python xxx.py程序就执行了.那么问题就来了,一个py文件是如何被python变成一系列的机器指令并执行的呢? 8. ...
 - Python逆向(二)—— pyc文件结构分析
		
一.前言 上一节我们知道了pyc文件是python在编译过程中出现的主要中间过程文件.pyc文件是二进制的,可以由python虚拟机直接执行的程序.分析pyc文件的文件结构对于实现python编译与反 ...
 - 关于python包,模块,.pyc文件和文件导入理解
		
参考文献 一.包 包是一个文件夹,用来存放模块和子包. 包里一般会有一个__init__.py的文件(也可以没有). 包里会有一个__pycache__文件夹,存放.py文件经解释器解释后的中间字节码 ...
 - Python编程时.py与.pyc文件的介绍
		
Python的程序中,是把原始程序代码放在.py文件里,而Python会在执行.py文件的时候.将.py形式的程序编译成中间式文件(byte-compiled)的.pyc文件,这么做的目的就是为了加快 ...
 - python运行时禁止生成pyc文件
		
方法 在环境变量文件~/.bashrc中添加 export PYTHONDONTWRITEBYTECODE=False source ~/.bashrc加载即可 如何从项目中删除所有.pyc文件 fi ...
 - .pyc文件的结构体PyCodeObject
		
python执行程序时生成的pyc文件里面是,PyCodeObject 的结构体构成,每个命名空间(函数名.import模块等)都会形成一个core block,一个python程序的所有命名空间生成 ...
 - 删除项目开发中的.pyc文件
		
在实际开发中python会自动生成很多pyc文件,但是这些pyc文件是不需要我们追踪的,删除了对项目也没有影响,下面是删除pyc文件的方法. Linux或Mac系统 find /tmp -name & ...
 
随机推荐
- 洛谷P1781 宇宙总统
			
https://www.luogu.org/problem/show?pid=1781 高精比较大小: #include<iostream> #include<cstdio> ...
 - Emacs中自动刷新dired缓冲区
			
Emacs中自动刷新dired缓冲区 在dired模式中,如果在不同buffer间切换,buffer不会自动更新,有时还需要手工按“g”键,比较麻烦,如下设置和代码能够在buffer切换和执行shel ...
 - 织梦修改文档HTML默认保存路径
			
\data\config.cache.inc.php $cfg_arcdir = '/a'; 改为 $cfg_arcdir = '/';
 - 使用SharePreferences存取数据(慕课笔记 )
			
0.视频地址:http://www.imooc.com/video/3265 1.使用SharePreferences存取数据: public class MainActivity extends A ...
 - Redis哨兵原理详解
			
一.概述 Redis哨兵(以下称哨兵)是为Redis提供一个高可靠解决方案,对一定程序上的错误,可以不需要人工干预自行解决. 哨兵功能还有监视.事件通知.配置功能.以下是哨兵的功能列表: 监控:不间断 ...
 - CDOJ 490 UESTC 490 Swap Game(思路,逆序对)
			
题意:有两种颜色的小球形成环,求最小交互次数使球相连. 题解:先解决另一个简单的问题,如果是一个链,把红球标记为1,蓝球标记为0,要排成升序需要多少次交换呢?答案是逆序对总数,原因是一次交互最多消除一 ...
 - Opencascade术语笔记。
			
1. chamfer 倒角 vs fillet 圆角: 2.boolean operatiron(布尔操作): common(相加),fuse(相交),cut(相减): 3.depressions( ...
 - Python 继承、派生、组合、接口、抽象类
			
继承是一种是的关系,和组合对比,组合是一种有的关系,这两者都是解决代用重用问题的 继承 注意:继承不是遗传,在显示角度中,是通过对象抽象成类,再把这些类抽象成一个,就是父类.是自下而上的过程,在程序中 ...
 - 课外作业1:将一个double类型的小数,按照四舍五入保留两位小数
			
package come.one01; public class One02 { public static void main(String[] args) { double numa = 3.14 ...
 - PAT (Basic Level) Practise (中文)-1039. 到底买不买(20)
			
PAT (Basic Level) Practise (中文)-1039. 到底买不买(20) http://www.patest.cn/contests/pat-b-practise/1039 小红 ...