python 字节码死磕
前言:
如果你跟我一样,对python的字节码感兴趣,想了解python的代码在内存中到底是怎么去运行的,那么你可以继续往下看,如果你是python新手,我建议你移步它处,本文适合有点基础的python读者。
如果你不知道怎么生成python的字节码文件,可以查阅我的 python 代码反汇编 的博文
python代码的执行过程:
- python代码编译成字节码【类似于汇编指令的中间语言】
- 字节码由python虚拟机来执行编译后的字节码
说明:
一个python语句会对应若个字节码指令,每个字节指令又对应着一个函数偏移量,可以理解为指令的ID
虚拟机一条一条执行字节码指令,从而完成程序的执行,而dis模块可以对CPython代码进行反汇编,生成字节码指令
dis.dis() 转化后的字节码格式如下:
源码行号 | 指令偏移量 | 指令符号 | 指令参数 | 实际参数值
说明: 不同版本的CPython 指令长度可能不同,但是 3.7的每条指令是2个字节,所以我们去看dis 生成的字节码指令集的时候,指令偏移量总是从0开始,每增加一条在原来的偏移量上增加2
故,指令偏移量的值,一般都是: 0 , 2 , 4, 6 , 8 , ... , 2n ( n>=0 )
变量指令解析
变量 — _const
LOAD_CONST :加载const 变量,比如数值,字符串等等, 一般用于传递给函数作为参数
案例一:
test(2,'hello')
对应的字节码指令
1 0 LOAD_NAME 0 (test) 2 LOAD_CONST 0 (2) 4 LOAD_CONST 1 ('hello') 6 CALL_FUNCTION 2 8 POP_TOP 10 LOAD_CONST 2 (None) 12 RETURN_VALUE
局部变量 — _FAST
LOAD_FAST :一般用于加载局部变量的值,也就是读取值,用于计算或者函数调用传传等
STORE_FAST :一般用于保存值到局部变量
案例二:
n = n / p
对应的字节码指令
1 0 LOAD_NAME 0 (n) 2 LOAD_NAME 1 (p) 4 BINARY_TRUE_DIVIDE 6 STORE_NAME 0 (n) 8 LOAD_CONST 0 (None) 10 RETURN_VALUE
说明: 函数的形参也是局部变量,那么如何区分局部变量中的形参呢?
形参是没有初始化的,所以如果发现发现操作的一个局部变量只有 LOAD_FAST 而没有 STORE_FAST,那么这个变量就是形参了。而其它的局部变量在使用之前肯定会使用STORE_FAST进行初始化。
案例三:
def test(arg1): num = 0 print(num, arg1)
对应的字节码指令
1 0 LOAD_CONST 0 (<code object test at 0x10546c150, file "code.py", line 1>) 2 LOAD_CONST 1 ('test') 4 MAKE_FUNCTION 0 6 STORE_NAME 0 (test) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE Disassembly of <code object test at 0x10546c150, file "code.py", line 1>: 2 0 LOAD_CONST 1 (0) 2 STORE_FAST 1 (num) 3 4 LOAD_GLOBAL 0 (print) 6 LOAD_FAST 1 (num) 8 LOAD_FAST 0 (arg1). #只有LOAD_FAST ,没有 STORE_FAST 10 CALL_FUNCTION 2 12 POP_TOP 14 LOAD_CONST 0 (None) 16 RETURN_VALUE
全局变量 — _GLOBAL
LOAD_GLOBAL : 用来加载全局变量, 包括制定函数名,类名,模块名等全局符号
STORE_GLOBAL :用来给全局变量赋值
案例四
def test(arg1): global age age = 20 print(age)
对应的字节码指令
1 0 LOAD_CONST 0 (<code object test at 0x1056e3150, file "code.py", line 1>) 2 LOAD_CONST 1 ('test') 4 MAKE_FUNCTION 0 6 STORE_NAME 0 (test) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE Disassembly of <code object test at 0x1056e3150, file "code.py", line 1>: 3 0 LOAD_CONST 1 (20) 2 STORE_GLOBAL 0 (age) 4 4 LOAD_GLOBAL 1 (print) 6 LOAD_GLOBAL 0 (age) 8 CALL_FUNCTION 1 10 POP_TOP 12 LOAD_CONST 0 (None) 14 RETURN_VALUE
常用数据类型
1.list
BUILD_LIST : 用于创建一个 list 结构
案例五
a = [1, 2]
对应的字节码指令
1 0 LOAD_CONST 0 (1) 2 LOAD_CONST 1 (2) 4 BUILD_LIST 2 6 STORE_NAME 0 (a) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE //程序结束
案例六
[ x for x in range(4) if x > 2 ]
对应的字节码
1 0 LOAD_CONST 0 (<code object <listcomp> at 0x10bffa150, file "code.py", line 1>) 2 LOAD_CONST 1 ('<listcomp>') 4 MAKE_FUNCTION 0 6 LOAD_NAME 0 (range) 8 LOAD_CONST 2 (4) 10 CALL_FUNCTION 1 12 GET_ITER 14 CALL_FUNCTION 1 16 POP_TOP 18 LOAD_CONST 3 (None) 20 RETURN_VALUE Disassembly of <code object <listcomp> at 0x10bffa150, file "code.py", line 1>: 1 0 BUILD_LIST 0 //创建 list , 为赋值给某变量,这种时候一般都是语法糖结构了 2 LOAD_FAST 0 (.0) >> 4 FOR_ITER 16 (to 22) //开启迭代循环 6 STORE_FAST 1 (x) //局部变量x 8 LOAD_FAST 1 (x) // 导入 x 10 LOAD_CONST 0 (2) // 导入 2 12 COMPARE_OP 4 (>) // x 与 2 进行比较,比较符号为 > 14 POP_JUMP_IF_FALSE 4 // 不满足条件就跳过 “出栈“ 动作,既,continue 到 " >> 4 FOR_ITER. “ 处 16 LOAD_FAST 1 (x) // 读取满足条件的局部变量x 18 LIST_APPEND 2 // 把满足条件的x 添加到list中 20 JUMP_ABSOLUTE 4 >> 22 RETURN_VALUE //程序结束
2.dict
BUILD_MAP : 用于创建一个空的dict
STORE_MAP : 用于初始化 dict 中的内容,赋值给变量
案例七
k = {'a': 1}
对应的字节码
1 0 LOAD_CONST 0 ('a') 2 LOAD_CONST 1 (1) 4 BUILD_MAP 1 6 STORE_NAME 0 (k) 8 LOAD_CONST 2 (None) 10 RETURN_VALUE
3.slice
BUILD_SLICE : 用于创建切片, 对于 list , tuple , 字符串都可以使用slice 的方式进行访问
BINARY_SUBSCR : 读取slice 的值
STORE_SUBSCR : slice 的值赋值给变量。
案例八
num = [1, 2, 3] a = num[1:2] b = num[0:1:1] num[1:2] = [10, 11]
对应的字节码
1 0 LOAD_CONST 0 (1) 2 LOAD_CONST 1 (2) 4 LOAD_CONST 2 (3) 6 BUILD_LIST 3 8 STORE_NAME 0 (num) 2 10 LOAD_NAME 0 (num) 12 LOAD_CONST 0 (1) 14 LOAD_CONST 1 (2) 16 BUILD_SLICE 2 #创建了一个切片 18 BINARY_SUBSCR #读取切片中的值 20 STORE_NAME 1 (a) #将读取切片中的值赋值给变量 a 3 22 LOAD_NAME 0 (num) 24 LOAD_CONST 3 (0) 26 LOAD_CONST 0 (1) 28 LOAD_CONST 0 (1) 30 BUILD_SLICE 3 32 BINARY_SUBSCR 34 STORE_NAME 2 (b) 4 36 LOAD_CONST 4 (10) 38 LOAD_CONST 5 (11) 40 BUILD_LIST 2 42 LOAD_NAME 0 (num) 44 LOAD_CONST 0 (1) 46 LOAD_CONST 1 (2) 48 BUILD_SLICE 2 50 STORE_SUBSCR 52 LOAD_CONST 6 (None) 54 RETURN_VALUE
4.循环
SETUP_LOOP :用于开始一个循环。
JUMP_ABSOLUTE: 结束循环
案例九
i = 0 while i < 10: i += 1
对应的字节码
1 0 LOAD_CONST 0 (0) 2 STORE_NAME 0 (i) 2 4 SETUP_LOOP 20 (to 26) // 循环开始处,26表示循环结束点 >> 6 LOAD_NAME 0 (i) // “>>" 表示循环切入点 8 LOAD_CONST 1 (10) 10 COMPARE_OP 0 (<) 12 POP_JUMP_IF_FALSE 24 3 14 LOAD_NAME 0 (i) 16 LOAD_CONST 2 (1) 18 INPLACE_ADD 20 STORE_NAME 0 (i) 22 JUMP_ABSOLUTE 6 // 逻辑上,循环在此处结束 >> 24 POP_BLOCK >> 26 LOAD_CONST 3 (None) 28 RETURN_VALUE
案例十
num = 0 for i in range(5): num += i
对应的字节码
1 0 LOAD_CONST 0 (0) 2 STORE_NAME 0 (num) 2 4 SETUP_LOOP 24 (to 30) //开始循环 6 LOAD_NAME 1 (range) 8 LOAD_CONST 1 (5) 10 CALL_FUNCTION 1 //调用range 函数 12 GET_ITER //获取迭代 range 的 iter >> 14 FOR_ITER 12 (to 28) //开始进行 range 的迭代 16 STORE_NAME 2 (i) 3 18 LOAD_NAME 0 (num) 20 LOAD_NAME 2 (i) 22 INPLACE_ADD 24 STORE_NAME 0 (num) 26 JUMP_ABSOLUTE 14 >> 28 POP_BLOCK >> 30 LOAD_CONST 2 (None) 32 RETURN_VALUE
5.if
POP_JUMP_IF_FALSE : 条件结果为 FALSE 则跳出 目标的偏移指令
JUMP_FORWARD : 直接跳转到目标便宜指令
COMPARE_OP: 比较指令
案例十一
num = 20 if num < 10: print('lt 10') elif num > 10: print('gt 10') else: print('eq 10')
对应的字节码
1 0 LOAD_CONST 0 (20) 2 STORE_NAME 0 (num) 2 4 LOAD_NAME 0 (num) 6 LOAD_CONST 1 (10) 8 COMPARE_OP 0 (<) 10 POP_JUMP_IF_FALSE 22 3 12 LOAD_NAME 1 (print) 14 LOAD_CONST 2 ('lt 10') 16 CALL_FUNCTION 1 18 POP_TOP 20 JUMP_FORWARD 26 (to 48) 4 >> 22 LOAD_NAME 0 (num) 24 LOAD_CONST 1 (10) 26 COMPARE_OP 4 (>) 28 POP_JUMP_IF_FALSE 40 5 30 LOAD_NAME 1 (print) 32 LOAD_CONST 3 ('gt 10') 34 CALL_FUNCTION 1 36 POP_TOP 38 JUMP_FORWARD 8 (to 48) 7 >> 40 LOAD_NAME 1 (print) 42 LOAD_CONST 4 ('eq 10') 44 CALL_FUNCTION 1 46 POP_TOP >> 48 LOAD_CONST 5 (None) 50 RETURN_VALUE
参考资料: