大家好,我是正在实战各种 AI 项目的程序员晚枫。
字节码如何在虚拟机中执行?理解栈式虚拟机的原理。这一讲,揭开 Python 虚拟机的核心机制。
📖 开篇:Python 代码是如何运行的? 我们写的 Python 代码,不会被直接执行,而是经过了这个过程:
1 2 3 4 5 源代码 (.py) ↓ 编译 字节码 (.pyc) ↓ 解释执行 Python 虚拟机 (PVM)
CPython 的虚拟机是一个栈式虚拟机 (Stack-based VM),所有操作都在操作数栈上进行。
🖥️ 虚拟机架构 核心:ceval.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 PyObject* _PyEval_EvalFrameDefault(PyThreadState* tstate, PyFrameObject* f, int throwflag) { for (;;) { opcode = NEXTOP(); oparg = 0 ; if (HAS_ARG(opcode)) { oparg = NEXTARG(); } switch (opcode) { case LOAD_FAST: PUSH(fastlocals[oparg]); break ; case STORE_FAST: fastlocals[oparg] = POP(); break ; case BINARY_ADD: v = POP(); w = POP(); x = PyNumber_Add(w, v); PUSH(x); break ; case RETURN_VALUE: return POP(); case ...: break ; } } }
主循环的三个步骤 1 2 3 fetch → 从内存读取指令(PC++) decode → 解析操作码和操作数 execute → 执行对应操作
📊 栈式虚拟机工作原理 操作数栈 虚拟机有一个栈(Stack),所有操作都在这个栈上进行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 执行 1 + 2: 初始状态: Stack: [] LOAD_CONST 1: Stack: [1] LOAD_CONST 2: Stack: [1, 2] BINARY_ADD: Pop 2 → w = 2 Pop 1 → v = 1 计算 w + v = 3 Push 3 Stack: [3] RETURN_VALUE: 返回 3,栈帧销毁
实际演示 1 2 3 4 5 6 import disdef add (a, b ): return a + b dis.dis(add)
1 2 3 4 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4 BINARY_ADD 6 RETURN_VALUE
每条指令对应的栈操作:
1 2 3 4 LOAD_FAST 0 → Stack: [a] LOAD_FAST 1 → Stack: [a, b] BINARY_ADD → Stack: [a+b] (弹出两个,push 一个) RETURN_VALUE → 返回 a+b
📚 栈帧结构 每个函数调用都有一个对应的栈帧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct _frame { PyObject_VAR_HEAD struct _frame *f_back ; PyCodeObject *f_code; PyObject *f_builtins; PyObject *f_globals; PyObject *f_locals; PyObject **f_valuestack; PyObject **f_stacktop; PyObject *f_localsplus[1 ]; int f_lasti; PyObject *exc_type, *exc_value, *exc_traceback; };
调用栈示例 1 2 3 4 5 6 7 8 9 10 def a (): return b() + 1 def b (): return c() * 2 def c (): return 42 print (a())
1 2 3 4 调用栈: frame_a: a() - waiting for b() result frame_b: b() - waiting for c() result frame_c: c() - returning 42
🔍 代码对象(PyCodeObject) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 typedef struct { PyObject_HEAD int co_argcount; int co_posonlyargcount; int co_kwonlyargcount; int co_nlocals; int co_stacksize; int co_flags; PyObject *co_code; PyObject *co_constants; PyObject *co_names; PyObject *co_varnames; PyObject *co_freevars; PyObject *co_cellvars; PyObject *co_filename; PyObject *co_name; int co_firstlineno; PyObject *co_lnotab; } PyCodeObject;
查看代码对象 1 2 3 4 5 6 7 def greet (name, greeting="Hello" ): return f"{greeting} , {name} !" print (greet.__code__.co_varnames) print (greet.__code__.co_argcount) print (greet.__code__.co_consts) print (greet.__code__.co_code)
⚡ 指令执行速度 指令分派开销 1 2 3 4 5 6 7 8 9 switch (opcode) { case LOAD_FAST: ... break ; case LOAD_CONST: ... break ; }
Python 3.11 的字节码优化让平均执行速度快了 10-15%,就是这个原因。
💡 本节作业 用 dis 分析一个复杂函数的字节码,画出栈操作过程 打印 add.__code__ 的所有属性,理解代码对象的结构 思考:为什么 Python 3.11 的字节码指令比之前少了? 🎯 本讲总结 虚拟机架构 :栈式虚拟机,通过 switch 分派执行字节码指令。
操作数栈 :所有操作都在栈上进行(压栈、出栈、运算)。
栈帧结构 :包含命名空间、执行状态、操作数栈,是函数调用的载体。
代码对象 :包含字节码、常量池、变量名等编译产物。
📚 推荐教材 《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》
🔗 课程导航 ← 上一讲:字节码编译 | 下一讲:常见字节码指令 →
💬 联系我 主营业务 :AI 编程培训、企业内训、技术咨询
🎓 AI 编程实战课程 想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!