解构解释器:理解Python字节码的背后
当 CPython 解释器执行你的程序时,它首先翻译成一系列字节码指令。字节码是Python虚拟机的一种中间语言,用作性能优化。
不是直接执行人类可读的源代码,而是使用紧凑的数字代码、常量和引用来表示编译器解析和语义分析的结果。这为重复执行程序或部分程序节省了时间和内存。例如,此编译步骤产生的字节码缓存在磁盘上的 .pyc 和 .pyo 文件中,这样第二次执行相同的Python文件时会更快。所有这些对程序员来说都是完全透明的。您不必知道这个中间翻译步骤发生了,或者Python虚拟机如何处理字节码。事实上,字节码格式被视为实现细节,并不保证在Python版本之间保持稳定或兼容。然而,人们可能会发现了解香肠是如何制作的并窥探 CPython 解释器提供的抽象背后的内容非常有启发性。至少了解一些内部工作原理可以帮助您编写性能更高的代码。
示例:让我们以这个简单的showMeByte()函数作为实验室示例和理解 Python 的字节码:
def showMeByte(name):
return "hello "+name+" !!!"
print(showMeByte("amit kumra"))
输出:
hello amit kumra !!!
CPython 在运行之前首先将我们的源代码翻译成一种中间语言。我们可以看到这个编译步骤的结果。每个函数都有一个__code__
属性(在Python 3 中),我们可以使用它来获取我们的 showMeByte函数使用的虚拟机指令、常量和变量:
例子:
def showMeByte(name):
return "hello "+name+" !!!"
print(showMeByte.__code__.co_code)
print(showMeByte.__code__.co_consts)
print(showMeByte.__code__.co_stacksize)
print(showMeByte.__code__.co_varnames)
print(showMeByte.__code__.co_flags)
print(showMeByte.__code__.co_name)
print(showMeByte.__code__.co_names)
输出:
b'd\x01|\x00\x17\x00d\x02\x17\x00S\x00'
(None, 'hello ', ' !!!')
2
('name',)
67
showMeByte
()
您可以看到 co_consts 包含我们的函数组装的问候字符串的一部分。常量和代码分开保存以节省内存空间。因此, Python不会在 co_code 指令流中重复实际的常量值,而是将常量单独存储在查找表中。然后,指令流可以引用具有查找表索引的常量。对于存储在 co_varnames 字段中的变量也是如此。 CPython 开发人员为我们提供了另一个称为反汇编器的工具,可以更轻松地检查字节码。 Python 的字节码反汇编程序位于标准库的 dis 模块中。所以我们可以导入它并在我们的 greet函数上调用dis.dis()
来获得一个更易于阅读的字节码表示:
例子:
import dis
def showMeByte(name):
return "hello "+name+" !!!"
dis.dis(showMeByte)
bytecode = dis.code_info(showMeByte)
print(bytecode)
bytecode = dis.Bytecode(showMeByte)
print(bytecode)
for i in bytecode:
print(i)
输出:
可执行指令或简单指令告诉处理器做什么。每条指令都包含一个操作码(opcode)。每个可执行指令生成一个机器语言指令。反汇编的主要工作是拆分指令流,并为其中的每个操作码赋予一个人类可读的名称,如 LOAD_CONST。您还可以看到常量和变量引用现在如何与字节码交错并完整打印,以使我们免于 co_const 和 co_varnames 表查找的心理体操。
它首先检索索引 1('Hello') 处的常量并将其放入堆栈。然后它加载 name 变量的内容并将它们放入堆栈。堆栈是用作虚拟机内部工作存储的数据结构。有不同类别的虚拟机,其中一种称为堆栈机。 CPython 的虚拟机就是这种堆栈机的实现。 CPython 的虚拟机就是这种堆栈机的实现。让我们假设堆栈开始是空的。执行完前两个操作码后,VM 的内容如下所示(0 是最顶部的元素):
0: ’amit kumra’(contents of “name”)
1: ‘hello ‘
BINARY_ADD指令将两个字符串值从堆栈中弹出,将它们连接起来,然后再次将结果压入堆栈:
0: ‘hello amit kumra’
然后还有另一个 LOAD_CONST 来获取堆栈上的感叹号字符串:
0 : ‘ !!!’
1:’Hello amit kumra’
下一个 BINARY_ADD 操作码再次将两者结合起来生成最终的问候字符串:
0: ‘hello amit kumra !!!’
最后一个字节码指令是RETURN_VALUE ,它告诉虚拟机当前位于堆栈顶部的是此函数的返回值,因此可以将其传递给调用者。所以,最后,我们追溯了我们的showMeCode()
函数是如何在 CPython 虚拟机内部执行的。