pyc文件
Python的原始代码在运行前都会被先编译成字节码,并把编译的结果保存到一个一个的PyCodeObject中,把PyCodeObject从内存中以marshal格式保存为文件,即pyc文件。python 内置库dis
,可以把二进制反编译CPython bytecode。二进制对应的bytecode可以参考: https://github.com/python/cpython/blob/2.7/Include/opcode.h ,其中bytecode所代表的意义:https://docs.python.org/2/library/dis.html
PyCodeObject
Python代码中Code.h中定义的PyCodeObject结构
1 | /* Bytecode object */ |
各字段的意义:
- argcount:参数的个数
- nlocals:局部变量的个数(包含参数在内)
- stacksize:堆栈的大小
- flags:用来表示参数中是否有
*args
或者**kwargs
- code:字节码
- names:全局变量,函数,类,类的方法的名称
- varnames:局部变量的名称(包含参数)
- consts:一个常量表,在marshal.c中有定义所有的类型
1 |
所有的PyCodeObject都是通过调用以下的函数得以运行的:PyObject * PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
, 这个函数是Python的一个重量极的函数,它的作用即是执行中间码,Python的代码都是通过调用这个函数来运行的
PyFrameObject这个数据结构:
1 | typedef struct _frame { |
重点关注下以下的成员:
1 | PyObject **f_stacktop; |
可见PyFrameObject中包含一个PyCodeObject的结构体指针f_code,PyFrameObject中的f_globals和f_locals这两个dict对象分别用于保存全局对象和局部对象,可以说,PyFrameObject结构就是PyCodeObject的运行环境,PyCodeObject结构是静态的,创建以后就一般不会再变,而PyFrameObject则是动态的,在创建以后,f_globals和f_locals这两个dict都经常会发生变化,并且一个PyCodeObject可能对应好几个的PyFrameObject中。
实例演示
- 新建test.py
1 | import dis |
- 编译成pyc
1 | python -m compileall test.py |
- 分析test.pyc
showfile.py
1 | import dis, marshal, struct, sys, time, types |
1 | showfile.py test.pyc > test.xml |
- test.xml
1 | magic 03f30d0a |
可以看到,整个test.pyc就是一个嵌套的PyCodeObject结构的组合,对于每个函数,或者类的方法,都会生成一个对应的PyCodeObject结构,并且模块还会生成额外的一个PyCodeObject结构,pyc文件中一共保存了5个PyCodeObject结构:
- 最外层的为模块test的PyCodeObject,其中的代码在import或者直接运行时会得到执行。
- 在module的PyCodeObject中嵌套了add函数的PyCodeObject结构以及world类的PyCodeObject结构。
- 在world类的PyCodeObject结构中,又嵌套了init方法和sayHello方法的PyCodeObject结构。
解析部分产生的opcode
1 | 1 0 LOAD_CONST 0 (-1) #加载consts数组中索引为0处的值,这里为数值-1 |