Python虚拟机底层实现原理的核心一句话:把.py文件先编译成字节码,再由C语言写的解释器主循环逐条执行,整个过程围绕栈帧、对象引用计数和垃圾回收展开。
Python虚拟机底层实现原理是什么?先从字节码看起
写Python代码时总给人一种“脚本语言很随意”的感觉,但背后藏着一套完整的编译执行流程,当你敲下python app.py,虚拟机先做两件事:把源码编译成字节码,再把字节码交给解释器执行,字节码是一种中间表示,长得有点像汇编但更抽象。
可以用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
这几条指令就是Python虚拟机要执行的“机器语言”,每一条都对应C源码里的一个case分支。
栈式虚拟机怎么理解
Python虚拟机采用栈式架构,指令操作数从栈上取,结果也压回栈。LOAD_FAST把局部变量a压栈,LOAD_FAST再把b压栈,BINARY_ADD弹出两个值相加后压回栈顶,RETURN_VALUE弹出栈顶值返回。
函数调用时,虚拟机会创建一个栈帧,每个栈帧维护一个值栈、一份局部变量数组、一个当前指令指针,当函数调用另一个函数,新栈帧压到调用栈上,像一摞盘子,调用结束,栈帧销毁,控制权交还上一层。
Python字节码执行流程详解:解释器主循环在忙什么
CPython源码里,最核心的执行逻辑在Python/ceval.c文件,这个文件中有一个巨大的switch语句,每一条字节码对应一个
case,很多人以为解释器有黑科技,其实就是一个超大循环加分支。
while (1) {
opcode = NEXT_OPCODE();
switch (opcode) {
case TARGET(LOAD_FAST): { ... }
case TARGET(BINARY_ADD): { ... }
case TARGET(RETURN_VALUE): { ... }
}
}
这个循环就是解释器主循环,它每次取一条字节码,执行对应操作,更新指令指针,继续下一条,近年来CPython做了一些优化,比如预测下一个指令、使用computed goto减少分支开销,但本质没变。
栈帧的数据结构长什么样
在Include/frameobject.h中,PyFrameObject结构体保存执行所需的所有状态,关键字段包括:
f_localsplus:局部变量和值栈共享的内存块f_valuestack:值栈指针f_frame:指向当前执行的代码对象f_state:指令指针和其他状态
每个栈帧创建时分配一块连续内存,栈顶指针随指令上下浮动,如果栈空间不够,会触发自动扩展。
一段函数的执行全过程
假设调用add(3, 5):
- 虚拟机创建
PyFrameObject,分配局部变量槽位。 - 参数
3和5存入局部变量数组。 - 主循环逐条执行:先加载
3,再加载5,相加得到8,压栈。 RETURN_VALUE把8从栈顶取出,作为函数返回值。- 栈帧销毁,返回值传给调用方。
整个过程全在C语言层完成,Python对象在C里用PyObject指针表示。
CPython和PyPy底层实现对比:为什么一个启动快一个跑得久
很多开发者会搜CPython和PyPy底层实现对比,这俩虽然都叫Python,但底层完全是两种生物。
| 对比维度 | CPython | PyPy |
|---|---|---|
| 执行方式 | 解释执行字节码 | 先解释,热点代码用JIT编译成机器码 |
| 内存管理 | 引用计数为主,分代回收为辅 | 更复杂的GC,常有更少停顿 |
| 启动速度 | 较快 | 较慢,需要预热JIT |
| 长时间运行性能 | 一般 | 多数情况下更快 |
| 源码结构 | C语言,ceval.c主循环清晰 |
RPython编写,带元追踪JIT框架 |
CPython像一位熟练的翻译,拿到字节码逐句翻译执行,PyPy像一位聪明的教练,发现某段代码反复跑,就把它编译成机器码直接跑,PyPy的JIT会跟踪循环,生成优化后的机器码,适合数值计算和长期运行的服务。
但CPython胜在生态兼容性,很多C扩展模块只认CPython的API,所以实际生产环境还是CPython为主。
如何阅读Python虚拟机源码?从哪几个文件下手
不少人一上来就钻进ceval.c,结果被几千行代码劝退。如何阅读Python虚拟机源码其实有更友好的路径。
准备源码和调试环境
先获取源码并编译一个调试版:
git clone https://github.com/python/cpython.git cd cpython ./configure --with-pydebug make -j
编译完成后,用gdb或lldb打断点、看栈帧、跟踪字节码执行,调试版会关闭一些优化,方便观察内部状态。
推荐阅读顺序
- 先看
Include/object.h:理解PyObject和引用计数 - 再看
Python/ceval.c:主循环和核心指令 - 然后看
Objects/目录:具体对象如listobject.c、
dictobject.c - 最后看
Python/frame.c:栈帧的创建和销毁
实操命令:在ceval.c的_PyEval_EvalFrameDefault函数打断点,运行一个简单脚本,观察每次字节码分发时的状态,这是理解虚拟机运作最直接的方式。
一个小练习
用dis反汇编一个递归函数,再在gdb里设置断点到BINARY_ADD,查看栈顶元素变化,你会直观看到栈帧如何一层层叠加。
Python虚拟机底层实现原理并不是玄学,它就是一段精妙的C语言循环,配合栈帧和对象体系完成动态语言的灵活性,理解了字节码、主循环和栈帧,再去读源码就不再是一团迷雾,CPython的源码本身就是一份很好的教材,关键在于找对入口。
Q&A:Python虚拟机底层实现原理常见疑问
Python虚拟机源码底层实现原理复杂吗?
复杂程度取决于你从哪一层看,如果只理解字节码和栈帧,花几天时间就能建立基本模型,如果要深入垃圾回收、对象模型和内存分配,就需要更多C语言和操作系统知识,但整体逻辑是线性的:编译成字节码,然后循环解释执行。
学习Python虚拟机底层原理需要什么基础?
至少需要熟悉C语言指针和结构体,知道函数调用栈是什么,了解汇编对理解字节码有帮助,但不是必须,Python官方文档和源码注释是主要参考,近年来社区编写的源码分析文章也降低了门槛。
Python虚拟机底层源码和Java虚拟机有什么区别?
Python虚拟机执行的是Python字节码,指令高度动态,很多操作需要类型判断和对象操作,Java虚拟机执行的是Java字节码,指令更接近静态类型,有更成熟的JIT编译,Python虚拟机启动轻量但解释执行较慢,Java虚拟机启动重但长时间运行性能更好,两者都维护调用栈,但内存管理和对象模型差异很大。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/641965.html





