PYC文件简介
基本格式¶
pyc文件一般由3个部分组成:
- 最开始4个字节是一个Maigc int, 标识此pyc的版本信息, 不同的版本的 Magic 都在 Python/import.c 内定义
- 接下来四个字节还是个int,是pyc产生的时间(1970.01.01到产生pyc时候的秒数)
- 接下来是个序列化了的 PyCodeObject(此结构在 Include/code.h 内定义),序列化方法在 Python/marshal.c 内定义
前两个字段的读写很简单,接下来咱们主要看一下 PyCodeObject 的序列化过程, 由于 PyCodeObject 内还有其他很多类型的 PyObject, 所以咱们从一般的 PyObject 的序列化开始看起:
PyObject的序列化¶
PyObject的序列化在 Python/marshal.c 内实现, 一般是先写入一个 byte 来标识此 PyObject 的类型, 每种 PyObject 对应的类型也在 Python/marshal.c 内定义:
//Python/marshal.c:22
#define TYPE_NULL '0'
#define TYPE_NONE 'N'
#define TYPE_FALSE 'F'
#define TYPE_TRUE 'T'
#define TYPE_STOPITER 'S'
#define TYPE_ELLIPSIS '.'
#define TYPE_INT 'i'
#define TYPE_INT64 'I'
#define TYPE_FLOAT 'f'
#define TYPE_BINARY_FLOAT 'g'
#define TYPE_COMPLEX 'x'
#define TYPE_BINARY_COMPLEX 'y'
#define TYPE_LONG 'l'
#define TYPE_STRING 's'
#define TYPE_INTERNED 't'
#define TYPE_STRINGREF 'R'
#define TYPE_TUPLE '('
#define TYPE_LIST '['
#define TYPE_DICT '{'
#define TYPE_CODE 'c'
#define TYPE_UNICODE 'u'
#define TYPE_UNKNOWN '?'
#define TYPE_SET '<'
#define TYPE_FROZENSET '>'
‘i’ | 4 bytes int |
‘I’ | 8 bytes int |
而 PyStringObject 的存储是这样的:
‘s’ | 4 bytes length | length bytes content(char[]) |
PyTupleObject 和 PyListObject 的存储分别是:
‘(‘ | 4 bytes length | length 个 PyObject |
‘[‘ | 4 bytes length | length 个 PyObject |
各种 PyObject 如何序列化,哪些内容被参与了序列化, 可以参看 Python/marshal.c 内的函数 w_object 函数, 接下来咱们着重看下前面提到的 PyCodeObject 的序列化:
PyCodeObject 的序列化¶
结构体 PyCodeObject 在 Include/code.h 中定义如下:
//Include/code.h
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) See
Objects/lnotab_notes.txt for details. */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
PyObject *co_weakreflist; /* to support weakrefs to code objects */
} PyCodeObject;
//...
else if (PyCode_Check(v)) {
PyCodeObject *co = (PyCodeObject *)v;
w_byte(TYPE_CODE, p);
w_long(co->co_argcount, p);
w_long(co->co_nlocals, p);
w_long(co->co_stacksize, p);
w_long(co->co_flags, p);
w_object(co->co_code, p);
w_object(co->co_consts, p);
w_object(co->co_names, p);
w_object(co->co_varnames, p);
w_object(co->co_freevars, p);
w_object(co->co_cellvars, p);
w_object(co->co_filename, p);
w_object(co->co_name, p);
w_long(co->co_firstlineno, p);
w_object(co->co_lnotab, p);
}
//...
- co_argcount : code需要的位置参数个数,不包括变长参数(*args和**kwargs)
- co_nlocals : code内所有的局部变量的个数,包括所有参数
- co_stacksize : code段运行时所需要的最大栈深度
- co_flags : 一些标识位,也在code.h里定义,注释很清楚,比如 CO_NOFREE(64) 表示此 PyCodeObject 内无 freevars 和 cellvars 等
- co_code : PyStringObject(‘s’), code对应的字节码(参看 Include/opcode.h 以及此文后续章节)
- co_consts : 所有常量组成的tuple
- co_names : code所用的到符号表, tuple类型,元素是字符串
- co_varnames : code所用到的局部变量名, tuple类型, 元素是 PyStringObject(‘s/t/R’)
- co_freevars : code所用到的freevar的变量名,tuple类型, 元素是 PyStringObject(‘s/t/R’)
- co_cellvars : code所用到的cellvar的变量名,tuple类型, 元素是 PyStringObject(‘s/t/R’)
- co_filename : PyStringObject(‘s’), 此code对应的py文件
- co_name : 此code的名称
- co_firstlineno : 此code对应的py文件里的第一行的行号
- co_lnotab : PyStringObject(‘s’),指令与行号的对应表
在 Python 代码中,每个作用域(或者叫block或者名字空间?)对应一个 PyCodeObject 对象, 所以会出现嵌套: 比如 一个 module 类 定义了 N 个 class, 每个 class 内又定义了 M 个方法. 每个 子作用域 对应的 PyCodeObject 会出现在它的 父作用域 对应的 PyCodeObject 的 co_consts 字段里.
接下来用个例子对上面的某些字段做个说明, 比如下面的python代码
#test.py
import struct def abc(c):
a=1
b=2
return struct.pack("S",a+b+c) var_a="abc"
var_b={var_a:123,"sec_key":abc}
字段 | 值 | 注释 |
---|---|---|
co_argcount | 0 | 模块没有参数 |
co_nlocals | 0 | 模块没有局部变量 |
co_stacksize | 3 | 栈最大尺寸 |
co_flags | 64 | CO_NOFREE |
co_code | ‘dx00x00d...’ | 字节码序列 |
co_consts | (-1, None, another-code-obj...) | 所有常量,包括模块里的 function,method |
co_names | (‘struct’, ‘abc’, ‘var_a’, ‘var_b’) | 此作用域内用到的所有符号 |
co_varnames | () | 局部变量名(模块没有局部变量) |
co_freevars | () | freevars |
co_cellvars | () | cellvars |
co_filename | test.py | 源码文件名 |
co_name | ‘<module>’ | code名字,模块名都是<module>,class是类名,func是函数名 |
co_firstlineno | 3 | 此code对应作用域的第一行的行号 |
co_lnotab | ‘x0cx02tx05x06x01’ | 行号表 |
这其中的 co_consts 里面的第三个元素是function abc的PyCodeObject, 我们来看下function abc的code的各字段:
字段 | 值 | 注释 |
---|---|---|
co_argcount | 1 | 1个参数c |
co_nlocals | 3 | 1个参数c, 两个局部变量a,b |
co_stacksize | 4 | 栈最大尺寸 |
co_flags | 67 | CO_OPTIMIZATION | CO_NEWLOCALS | CO_NOFREE |
co_code | ‘dx01x00}...’ | 字节码序列 |
co_consts | (None, 1, 2, ‘S’) | 函数里用到的所有常量 |
co_names | (‘struct’, ‘pack’) | 此作用域内用到的所有符号 |
co_varnames | (‘c’, ‘a’, ‘b’) | 局部变量名 |
co_freevars | () | freevars |
co_cellvars | () | cellvars |
co_filename | test.py | 源码文件名 |
co_name | ‘abc’ | code名字,func是函数名 |
co_firstlineno | 5 | 此code对应作用域的第一行的行号 |
co_lnotab | ‘x00x01x06x01x06x01’ | 行号表 |
图解 test.py 与 test.pyc 的结构关系¶
co_freevars 与 co_cellvars¶
这两个字段是给 Closure 准备的(Python里没有真正的Closure),通俗的说就是函数嵌套的时候用的到,比如:
def outter(o1,o2):
fc1=o1+o2
fc2=o1*o2
def inner(i):
return (fc1+fc2)*i
- 对于 inner 函数, fc1 和 fc2 既不是局部变量也不是全局变量,他引用自外层函数, 则 fc1 和 fc2 是 inner 的 freevars
PyStringObject 的序列化¶
通过前面的介绍,你可能会发现PyCodeObject里面的用到str(‘s’)类型的地方很多,什么co_consts啊,co_names,co_varnames,co_freevars,co_cellvars等等都是str的tuple,里面的str重复的也比较多,要是一股脑这么写进去可能会占用很大空间,于是w_object里对PyStringObject的序列化又多加了两种类型:
- ‘t’ : interned-string, 暂时可能简单理解为 pyc 里回重复出现的 str, 这个类型就是简单的把 str 的类型 ‘s’ 改成 ‘t’ 了,后面还是跟 length(4bytes) 和 content(char[])
- ‘R’ : 指向 interned-string 的字符串引用, ‘R’后面跟4个 bytea 的引用序号
具体看 Python/marshal.c 内 w_object 的相关实现:
//...
else if (PyString_CheckExact(v)) {
if (p->strings && PyString_CHECK_INTERNED(v)) {
PyObject *o = PyDict_GetItem(p->strings, v);
if (o) {
long w = PyInt_AsLong(o);
w_byte(TYPE_STRINGREF, p);
w_long(w, p);
goto exit;
}
else {
int ok;
o = PyInt_FromSsize_t(PyDict_Size(p->strings));
ok = o &&
PyDict_SetItem(p->strings, v, o) >= 0;
Py_XDECREF(o);
if (!ok) {
p->depth--;
p->error = WFERR_UNMARSHALLABLE;
return;
}
w_byte(TYPE_INTERNED, p);
}
}
else {
w_byte(TYPE_STRING, p);
}
n = PyString_GET_SIZE(v);
if (n > INT_MAX) {
/* huge strings are not supported */
p->depth--;
p->error = WFERR_UNMARSHALLABLE;
return;
}
w_long((long)n, p);
w_string(PyString_AS_STRING(v), (int)n, p);
}
//...
行号对照表¶
co_lnotab可以看做是(字节码在co_opcode中的index增量(1 byte),对应的源码的行号增量(1-bytes))顺次串成的的字节数组(字符串).
字段 co_code 与 Python的OPCODE¶
PyCodeObject 的 co_code 字段就是 python opcode 组成的序列, 具体有哪些 opcode可以参看 Include/opcode.h , 这里面有些opcode有参数,有些没有参数, 从opcode.h内的代码段:
#define HAVE_ARGUMENT 90 /* Opcodes from here have an argument: */
//...
#define HAS_ARG(op) ((op) >= HAVE_ARGUMENT)
可以看出,大于等与90的opcode是有参数的, 有参数的opcode的参数是两个 unsigned byte, 第一个是操作数, 第二个目前固定为0x00但是不能省略,举例来说,我要把当前code的co_consts里的第二个常量(index是1)载入到栈顶,则对应的opcode序列为: |LOAD_CONST|0x01|0x00| ,也就是 '0x640x010x00'
其他的opcode大都类似,主要是对函数调用栈以及co_consts, co_varnames, co_freevars, co_cellvars的操作, 还有些BUILD_CLASS, BUILD_MAP, BUILD_LIST, BUILD_TUPLE, BUILD_SLICE, MAKE_FUNCTION, MAKE_CLOSURE 等构建对象的特殊指令.
有参数的 opcode 的参数的参数大多时候是个 index, 比如 LOAD_CONST 1 的 1 就是个 index, 表示把当前 PyCodeObject.co_consts[1] 这个常量载入到栈顶, LOAD_FAST 2 则是把 PyCodeObject.co_varnames[2] 这个局部变量载入到栈顶;而 MAKE_FUNCTION 2 则表示栈顶code-obj对应的 function有两个默认参数.
前面 test.pyc 之外的东西, 比如 class/closure 的创建, 也都逃不过这些指令, 具体每个指令的解释和用法可以参看 : http://docs.python.org/release/2.7/library/dis.html#python-bytecode-instructions
Page(Article) Information / 页面(文章)信息:¶
- Author : KDr2
- License : | Creative Commons BY-NC-ND 3.0 | CC3.0 : 自由转载-非商用-非衍生-保持署名 |
- Hosted on DreamHost
PYC文件简介的更多相关文章
- Git .gitignore文件简介及使用
Git .gitignore文件简介及使用 By:授客 QQ:1033553122 .gitignore 这个文件的作用就是告诉Git哪些文件不需要添加到版本管理中.实际项目中,很多文件都是不需要版本 ...
- Django框架-目录文件简介
Rhel6.5 Django1.10 Python3.5 Django框架-目录文件简介 1.介绍Django Django:一个可以使Web开发工作愉快并且高效的Web开发框架. 使用Django, ...
- Android资源文件简介
Android资源文件简介 1. Android应用资源的作用 (1) Android项目中文件分类 在Android工程中, 文件主要分为下面几类 : 界面布局文件, Java src源文件, 资源 ...
- .pyc文件是什么?
一个.py文件就是一个模块,而模块名就是文件名,如module.py的模块名就是module.如果module.py文件里定义了一些函数和变量,而外部文件如test_module.py想使用这些函数或 ...
- pyc 文件反编译 py uncompyle2
一.工具 https://github.com/Mysterie/uncompyle2 -----------------------2016-6-3 11:24:19-- source:[1]用un ...
- properties文件简介及其常用Java操作
一.properties文件简介 java中的properties文件是一种配置文件,主要用于表达配置信息,文件类型为*.properties,格式为文本文件,文件的内容是格式是"键=值&q ...
- python py生成为pyc文件
生成单个pyc文件 python就是个好东西,它提供了内置的类库来实现把py文件编译为pyc文件,这个模块就是 py_compile 模块. 使用方法非常简单,如下所示,直接在idle中,就可以把一个 ...
- [安卓]AndroidManifest.xml文件简介及结构
1.AndroidManifest.xml文件简介: 每个应用程序在它的根目录中都必须要有一个AndroidManifest.xml(名字须精确一致)文件.这个清单把应用程序的基本信息提交给Andro ...
- (转)IOS之Info.plist文件简介
原文:IOS之Info.plist文件简介 http://www.apkbus.com/android-130240-1-1.html (出处: Android开发论坛 - 安卓开发论坛 - Andr ...
随机推荐
- debian 下的vi 上下左右键问题
小白一只,查了一下vi的版本信息 发现好像是vim 于是把~/.vimrc 变量设置了一下就好了。 将set compatible 设置成set nocompatible . 这是因为系统会默认vim ...
- php面试题四
php面试题四 一.总结 二.php面试题四 01. 输出为 Mozilla/4.0(compatible;MSIE5.01;Window NT 5.0)时,可能的输出语句是: A.$_S ...
- 使用Array和[]创建数组的区别
原文 简书原文:https://www.jianshu.com/p/57a337d20aea 大纲 前言 对使用Array和[]创建数组的区别的解释 个人理解 前言 JS定义数组变量时,在不需要给定数 ...
- 【Nutch2.2.1基础教程之6】Nutch2.2.1抓取流程 分类: H3_NUTCH 2014-08-15 21:39 2530人阅读 评论(1) 收藏
一.抓取流程概述 1.nutch抓取流程 当使用crawl命令进行抓取任务时,其基本流程步骤如下: (1)InjectorJob 开始第一个迭代 (2)GeneratorJob (3)FetcherJ ...
- php实现找链表中环的入口节点(画图、看评论)
php实现找链表中环的入口节点(画图.看评论) 一.总结 画图.看评论 二.php实现找链表中环的入口节点 题目描述: 一个链表中包含环,请找出该链表的环的入口结点. 三.代码 第一步,找环中相汇点. ...
- 【心情】bjdldrz
如果我对勇士的记忆停留在73胜10负,
- [SCSS] Reuse Styles with the SCSS @extend Directive
We can write reusable styles with the SCSS @extend or @mixin directives. Which one is better? It dep ...
- 【图解】Web前端实现相似Excel的电子表格
在本文中,我将用图解的方式用Wijmo(JavaScript库)中的SpreadJS来一步一步实现网页上的电子表格产品SpreadSheet(比如可构建Office 365 Excel产品.Go ...
- mui常用功能链接地址
1.下拉刷新mui.pullToRefresh插件http://ask.dcloud.net.cn/article/12152.打包app权限列表http://ask.dcloud.net.cn/ar ...
- amazeui时间组件测试
amazeui时间组件测试 一.总结 一句话总结: 1.图标是字体样式:input右侧的字体图标是字体样式,所以要引入字体文件,随便找一个项目把里面的字体文件弄过来就ok了 2.多看官方文档:这个时间 ...