python字节码
Python解释器
有时候我们会把 Python 的 REPL (命令行下Python的交互环境)当作解释器,它会将源代码编译为字节码并执行。Python 解释器是一个模拟堆栈机器的虚拟机,仅使用多个栈来完成操作。也可以看看 500行实现一个解释器
在解释器接手之前,Python 会执行其他3个步骤:词法分析,语法解析和编译。这三步合起来把源代码转换成 code object,它包含着解释器可以理解的指令。而解释器的工作就是解释 code object 中的指令。
code object
Python字节码
在我们导入 python 脚本时在目录下会生成个一个相应的 pyc 文件,这是 python 解释器生成的字节码,是为了加速下一次的装载。
我们写一个简单的函数,并打印其字节码
dis
1 | def sample(n:int) -> bool: |
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 0 LOAD_FAST 0 (n) |
总共分为五列,分别是
- 字节码对应的在源代码中的行号
- 该字节码在字节码串中的第几个字节,也就是该字节码的索引
- 字节码的人类可读的名字
- 字节码参数
- 字节码参数的内容提示
再看
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