你有没有想过,当你运行一个 Python 脚本时,电脑到底“看”到了什么?很多人以为 Python 是直接一行行解释执行的,其实没那么简单。在你看不见的地方,你的代码先被编译成了字节码 —— 一种更接近机器但又跨平台的中间指令。
从一个简单的函数说起
比如我们写了一个加法函数:
def add(a, b):
return a + b
看起来很简单对吧?但 Python 解释器不会直接执行这三行文本。它会先把这段代码编译成字节码,然后由 Python 虚拟机(PVM)一条条运行这些字节码指令。
怎么看到字节码?用 dis 模块
Python 自带了一个叫 dis 的模块,可以反汇编函数的字节码。继续上面的例子:
import dis
def add(a, b):
return a + b
dis.dis(add)
运行后你会看到类似这样的输出:
2 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 RETURN_VALUE
每一行都是一条字节码指令。比如 LOAD_FAST 是从局部变量中快速加载值,BINARY_ADD 是做加法操作,最后 RETURN_VALUE 把结果返回。
字节码长什么样?.pyc 文件了解一下
你写的 .py 文件第一次被导入时,Python 会生成一个同名的 .pyc 文件,里面存的就是编译后的字节码。比如你有个 utils.py,导入一次后就会在 __pycache__ 目录下看到类似 utils.cpython-39.pyc 的文件。
这个 .pyc 文件是二进制的,不能直接读,但它让下次导入更快 —— 因为跳过了重新编译的步骤。就像你常去的咖啡馆记住了你的口味,不用每次都重点一遍。
动手改字节码?真有人干过
虽然不推荐,但你可以修改函数的字节码来改变它的行为。比如下面这个函数:
def hello():
print("你好")
它的字节码里,print 对应的是字符串 "你好"。理论上,如果你能定位到这个常量,替换成 "再见",那函数就真的会打印“再见”了。
当然,实际操作要小心,改错了程序可能直接崩溃。但这说明了一件事:Python 的灵活性不仅在语法层面,连执行过程都能“动手术”。
为什么了解字节码有用?
大多数时候你不需要关心字节码,写好逻辑就行。但在一些场景下,它能帮你理解性能问题。比如循环里频繁调用全局变量,字节码会显示每次都要走 LOAD_GLOBAL,比 LOAD_FAST 慢。这时候你就明白,为啥建议把全局变量缓存到局部了。
再比如调试某些奇怪的行为,看字节码能发现代码是否按你预期的方式编译。有时候你以为是语法糖,其实是完全不同的指令路径。
小结一下关键点
Python 不是纯解释执行,而是先编译成字节码;字节码由 CPython 虚拟机运行;可以用 dis 模块查看;.pyc 文件就是字节码的持久化形式;理解字节码有助于深入掌握 Python 的运行机制。
下次你运行一个脚本,不妨想想:那几行代码,已经被翻译成什么样的“暗语”了?