python字节码

Python解释器

有时候我们会把 Python 的 REPL (命令行下Python的交互环境)当作解释器,它会将源代码编译为字节码并执行。Python 解释器是一个模拟堆栈机器的虚拟机,仅使用多个栈来完成操作。也可以看看 500行实现一个解释器

在解释器接手之前,Python 会执行其他3个步骤:词法分析,语法解析和编译。这三步合起来把源代码转换成 code object,它包含着解释器可以理解的指令。而解释器的工作就是解释 code object 中的指令。

code object

Python字节码

在我们导入 python 脚本时在目录下会生成个一个相应的 pyc 文件,这是 python 解释器生成的字节码,是为了加速下一次的装载。

我们写一个简单的函数,并打印其字节码

dis

1
2
3
4
5
def sample(n:int) -> bool:
if n >= 1: return True
return False

print(sample.__code__.co_code)

sample.__code__ 是与其关联的 code object,而 sample.__code__.co_code 就是它的字节码。

输出:

b’|\x00\x00d\x01\x00k\x05\x00r\x10\x00d\x02\x00Sd\x03\x00S’

这是字节码组成的字节串,让我们把它转换成二进制

list(bytearray(sample.__code__.co_code)) [124, 0, 0, 100, 1, 0, 107, 5, 0, 114, 16, 0, 100, 2, 0, 83, 100, 3, 0, 83]

看,这写依然无法理解,这时我们借助标准库 dis(自节码反汇编器) 来查看字节码

dis.dis(sample)

1
2
3
4
5
6
7
8
9
10
2           0 LOAD_FAST                0 (n)
3 LOAD_CONST 1 (1)
6 COMPARE_OP 5 (>=)
9 POP_JUMP_IF_FALSE 16

3 12 LOAD_CONST 2 (True)
15 RETURN_VALUE

4 >> 16 LOAD_CONST 3 (False)
19 RETURN_VALUE

总共分为五列,分别是

  • 字节码对应的在源代码中的行号
  • 该字节码在字节码串中的第几个字节,也就是该字节码的索引
  • 字节码的人类可读的名字
  • 字节码参数
  • 字节码参数的内容提示

再看

dis.opname[124] ‘LOAD_FAST’ dis.opname[100] ‘LOAD_CONST’

这样我们就可以和之前的字节码对应了。当然这还没有完,我们还不理解这些操作码代表什么含义,打开 python文档 搜索 LOAD_FAST,你可以看到是将 co_varnames[var_num] 放进堆栈中(更高级的目前我也是看不懂),

Python 使用两个字节表示指令参数,如果Python使用一个字节,每个 code object 你只能有256个,而用两个字节,就增加到了256的平方,65536个

Frame

Frame 包含了一段代码运行所需要的信息与上下文环境。Frame 在代码执行时被动态地创建与销毁,每一个 Frame 的创建对应一次函数调用,所以每一个 Frame 都有一个 code object 与其关联,同时一个code object 可以拥有多个 Frame,因为一个函数可以递归调用自己多次。

比如你有一个函数递归的调用自己10次,这时有11个 frame。总的来说 Python 程序的每个作用域有一个 frame,比如每个 module、每个函数调用、每个类定义。这跟数据栈是不同的。

未完待续

It’s interesting