PYC文件簡介


PYC文件簡介

不說廢話,這里說的pyc文件就是 Python 程序編譯后得到的字節碼文件 (py->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 '>'
之后是 PyObject 的具體數據內容, 變長的對象(str, tuple, list 等)往往還包含了一個 4 bytes 的 len, 比如 PyIntObject 的存儲可能是這樣的:
‘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;
 
我們再看 Python/marshal.c 里面的 w_object 函數,從中找出寫 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);
}
//...
 
根據上面兩段代碼,我們很容易就能看到 PyCodeObject 里哪些字段需要參數序列化了,我們就挨個解釋下需要序列化的字段們:
  • 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}
它編譯后對應的 pyc 在 magictime 后就放着一個 PyCodeObject 對象, 這個對象的個字段指如下
test.py對應PyCodeObject中各字段的值
字段 注釋
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的各字段:

function abc 對應PyCodeObject中各字段的值
字段 注釋
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 的結構關系



../_images/pyc_format_example_0.png

co_freevars 與 co_cellvars

這兩個字段是給 Closure 准備的(Python里沒有真正的Closure),通俗的說就是函數嵌套的時候用的到,比如:

 
def outter(o1,o2):
fc1=o1+o2
fc2=o1*o2
def inner(i):
return (fc1+fc2)*i
對於 outter 函數來說,他的局部變量 fc1 和 fc2 被它內部嵌套的函數所引用,則 fc1 和 fc2 變成它的 cellvars 而不是局部變量 varnames
  • 對於 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.cw_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 / 頁面(文章)信息:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM