python源码分析笔记(8)

本文深入探讨Python程序的执行过程,包括解释器、字节码、PyCodeObject及其在pyc文件中的作用。分析了Python编译后的byte-code序列,以及import时pyc文件的生成。此外,还介绍了如何解析pyc文件和使用dis模块展示字节码指令信息。
摘要由CSDN通过智能技术生成

1.Python程序的执行过程

实际上Python和java,C#执行原理都可以用两个词概括,------虚拟机,字节码

Python有一个非常核心的东西,这个东西被称为解释器。当我运行一个程序时,例如 python my-program.py ,Python解释器立即被激活,然后开始执行,在运行之前,还要完成一个复制的工作,编译py.文件,结果主要产生一组Python的byte-code(字节码),然后将编译结果交给Python的虚拟机。

 

[demo.py]
class A:
    pass


def fun():
    pass

a = A()
fun()

当我们在执行demo.py时,编译结果会产生PyCodeObject对象和pyc文件。在程序运行期间,编译结果存在于内存的PyCodeObject对象中,而Python结束运行后,编译结果又被保存到了pyc文件,当下一次运行相同的程序时,Python会根据pyc文件中记录的编译结果直接建立内存中的PyCodeObject对象,而不用再次编译。

python中PyCodeObject的声明:

对于代码中的一个Code Block,会创建一个PyCodeObject对象与这段代码对应,对应上文的demo.py,编译完成后总共会创建三个PyCodeObject对象,一个对应demo.py整个文件的,一个是对应class A所代表的Code Block,而最后一个对应def Fun所代表的Code Block.

2.pyc文件

每一个PyCodeObject对象中包含了每一个Code Block中Python源代码经过编译后得到的byte code序列。而前面提到,Python会将这些字节码序列和PyCodeObject对象一起存储在pyc文件。但是我们在命令行敲下python demo.py时并没有生成pyc文件,原因是有写程序可能执行一次就再也不执行了,没必要生成其对应的pyc文件。但是假如碰到import demo的动态加载动作之后,python就会产生pyc文件了。意味着如果Python碰到import demo语句,会首先到设定好的PATH中寻找abc.pyc。如果没有,只发现了demo.py,那么就会编译成PyCodeObject对应的中间结果,然后创建pyc文件,接下来Python才会对abc.pyc进行import动作。

其实pyc是一个二进制文件,那么python如何解释这一堆看上去毫无意义的字节流就至关重要了。要了解pyc文件的格式,首先必须要清楚PyCodeObject中每一个域都表示什么含义。

3.Python的字节码

关于Python的编译结果,我们还剩下最后一个话题了,那就是Python的字节码,这里只会做一个简单的介绍,我们知道Python源代码在执行前会被编译成Python的字节码指令序列,Python虚拟机就是根据这些字节码进行一系列的操作。在Python安装目录的Include文件夹中,opcode.h保存了这些字节码序列

#define STOP_CODE   0
#define POP_TOP     1
#define ROT_TWO     2
#define ROT_THREE   3
#define DUP_TOP     4
#define ROT_FOUR    5
#define NOP     9

#define UNARY_POSITIVE  10
#define UNARY_NEGATIVE  11
#define UNARY_NOT   12
#define UNARY_CONVERT   13

#define UNARY_INVERT    15

#define LIST_APPEND 18
#define BINARY_POWER    19

#define BINARY_MULTIPLY 20
#define BINARY_DIVIDE   21
#define BINARY_MODULO   22
#define BINARY_ADD  23
#define BINARY_SUBTRACT 24
#define BINARY_SUBSCR   25
#define BINARY_FLOOR_DIVIDE 26
#define BINARY_TRUE_DIVIDE 27
#define INPLACE_FLOOR_DIVIDE 28
#define INPLACE_TRUE_DIVIDE 29

#define SLICE       30
/* Also uses 31-33 */

#define STORE_SLICE 40
/* Also uses 41-43 */

#define DELETE_SLICE    50
/* Also uses 51-53 */

#define STORE_MAP   54
#define INPLACE_ADD 55
#define INPLACE_SUBTRACT    56
#define INPLACE_MULTIPLY    57
#define INPLACE_DIVIDE  58
#define INPLACE_MODULO  59
#define STORE_SUBSCR    60
#define DELETE_SUBSCR   61

#define BINARY_LSHIFT   62
#define BINARY_RSHIFT   63
#define BINARY_AND  64
#define BINARY_XOR  65
#define BINARY_OR   66
#define INPLACE_POWER   67
#define GET_ITER    68

#define PRINT_EXPR  70
#define PRINT_ITEM  71
#define PRINT_NEWLINE   72
#define PRINT_ITEM_TO   73
#define PRINT_NEWLINE_TO 74
#define INPLACE_LSHIFT  75
#define INPLACE_RSHIFT  76
#define INPLACE_AND 77
#define INPLACE_XOR 78
#define INPLACE_OR  79
#define BREAK_LOOP  80
#define WITH_CLEANUP    81
#define LOAD_LOCALS 82
#define RETURN_VALUE    83
#define IMPORT_STAR 84
#define EXEC_STMT   85
#define YIELD_VALUE 86
#define POP_BLOCK   87
#define END_FINALLY 88
#define BUILD_CLASS 89

#define HAVE_ARGUMENT   90  /* Opcodes from here have an argument: */

#define STORE_NAME  90  /* Index in name list */
#define DELETE_NAME 91  /* "" */
#define UNPACK_SEQUENCE 92  /* Number of sequence items */
#define FOR_ITER    93

#define STORE_ATTR  95  /* Index in name list */
#define DELETE_ATTR 96  /* "" */
#define STORE_GLOBAL    97  /* "" */
#define DELETE_GLOBAL   98  /* "" */
#define DUP_TOPX    99  /* number of items to duplicate */
#define LOAD_CONST  100 /* Index in const list */
#define LOAD_NAME   101 /* Index in name list */
#define BUILD_TUPLE 102 /* Number of tuple items */
#define BUILD_LIST  103 /* Number of list items */
#define BUILD_MAP   104 /* Always zero for now */
#define LOAD_ATTR   105 /* Index in name list */
#define COMPARE_OP  106 /* Comparison operator */
#define IMPORT_NAME 107 /* Index in name list */
#define IMPORT_FROM 108 /* Index in name list */

#define JUMP_FORWARD    110 /* Number of bytes to skip */
#define JUMP_IF_FALSE   111 /* "" */
#define JUMP_IF_TRUE    112 /* "" */
#define JUMP_ABSOLUTE   113 /* Target byte offset from beginning of code */

#define LOAD_GLOBAL 116 /* Index in name list */

#define CONTINUE_LOOP   119 /* Start of loop (absolute) */
#define SETUP_LOOP  120 /* Target address (relative) */
#define SETUP_EXCEPT    121 /* "" */
#define SETUP_FINALLY   122 /* "" */

#define LOAD_FAST   124 /* Local variable number */
#define STORE_FAST  125 /* Local variable number */
#define DELETE_FAST 126 /* Local variable number */

#define RAISE_VARARGS   130 /* Number of raise arguments (1, 2 or 3) */
/* CALL_FUNCTION_XXX opcodes defined below depend on this definition */
#define CALL_FUNCTION   131 /* #args + (#kwargs<<8) */
#define MAKE_FUNCTION   132 /* #defaults */
#define BUILD_SLICE     133 /* Number of items */

#define MAKE_CLOSURE    134     /* #free vars */
#define LOAD_CLOSURE    135     /* Load free variable from closure */
#define LOAD_DEREF      136     /* Load and dereference from closure cell */ 
#define STORE_DEREF     137     /* Store into cell */ 

/* The next 3 opcodes must be contiguous and satisfy
   (CALL_FUNCTION_VAR - CALL_FUNCTION) & 3 == 1  */
#define CALL_FUNCTION_VAR          140  /* #args + (#kwargs<<8) */
#define CALL_FUNCTION_KW           141  /* #args + (#kwargs<<8) */
#define CALL_FUNCTION_VAR_KW       142  /* #args + (#kwargs<<8) */

/* Support for opargs more than 16 bits long */
#define EXTENDED_ARG  143

4.解析pyc文件

好了到现在,关于PyCodeObject的pyc文件已经有了初步了解,我们可以用PycParser的工程对pyc进行解析,转化为可视的XML,在co_consts中,包含了另外的PyCodeObject,同时还包含了别的对象,实际上在co_consts中包含了Python源文件的中所定义的所有常量对象,即除了PyStringObject对象之外的所有对象。PyStringObject通常保存在co_names和co_varnames。

我们知道在生成pyc时,会将PyCodeObject对象中的字节码序列一起写入pyc文件中,而且这个pyc文件还记录了每一条字节码指令与Python源代码行号的对应关系,保存在co_Inotab中。

而 Python 库中 dis 的 dis 方法可以对 code对象 进行解析。接收 code对象,输出 字节码指令信息。

dis.dis 的输出:

  • 第一列,是 字节码指令 对应的 源代码 在 Python 程序中的行数
  • 第二列,是当前 字节码指令 在 co_code 中的偏移位置
  • 第三列,当前的字节码指令
  • 第四列,当前字节码指令的参数

test.py

import sys

a = 1

def b():
    print a
    a = 2
    print a
>>> source = open('/Users/chao/Desktop/test.py').read()
>>> co = compile(source, 'test.py', 'exec')
>>> import dis
>>> dis.dis(co)
  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (sys)
              9 STORE_NAME               0 (sys)

  3          12 LOAD_CONST               2 (1)
             15 STORE_NAME               1 (a)

  5          18 LOAD_CONST               3 (<code object b at 0x1005dc930, file "test.py", line 5>)
             21 MAKE_FUNCTION            0
             24 STORE_NAME               2 (b)
             27 LOAD_CONST               1 (None)
             30 RETURN_VALUE
>>> type(co)
<type 'code'>
>>> dir(co)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__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_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
>>> print co.co_names
('sys', 'a', 'b')
>>> print co.co_name
<module>
>>> print co.co_filename
test.py

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值