大家好,我是正在实战各种 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
// Python/ceval.c
PyObject* _PyEval_EvalFrameDefault(PyThreadState* tstate,
PyFrameObject* f,
int throwflag)
{
// 这是 CPython 最核心的函数!
// 负责执行字节码指令的主循环

for (;;) {
// 1. 获取下一条指令
opcode = NEXTOP();
// opcode 是 8 位的操作码

// 2. 获取操作数(如果有)
oparg = 0;
if (HAS_ARG(opcode)) {
oparg = NEXTARG();
}

// 3. 分发到对应的处理函数
switch (opcode) {
case LOAD_FAST:
// LOAD_FAST 实现:把局部变量压入栈
PUSH(fastlocals[oparg]);
break;

case STORE_FAST:
// 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 dis

def 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()) # (42 * 2) + 1 = 85
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; // 字节码指令序列(bytes)

// 常量和变量
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) # ('name', 'greeting')
print(greet.__code__.co_argcount) # 1
print(greet.__code__.co_consts) # 默认参数
print(greet.__code__.co_code) # 字节码 bytes

⚡ 指令执行速度

指令分派开销

1
2
3
4
5
6
7
8
9
// Python 3.10 及之前:巨大的 switch 语句
switch (opcode) {
case LOAD_FAST: ... break;
case LOAD_CONST: ... break;
// ... 几百条 case ...
}

// Python 3.11+:间接分派(Indirect threading)
// 用跳转表替代 switch,跳转目标提前计算

Python 3.11 的字节码优化让平均执行速度快了 10-15%,就是这个原因。


💡 本节作业

  1. dis 分析一个复杂函数的字节码,画出栈操作过程
  2. 打印 add.__code__ 的所有属性,理解代码对象的结构
  3. 思考:为什么 Python 3.11 的字节码指令比之前少了?

🎯 本讲总结

虚拟机架构:栈式虚拟机,通过 switch 分派执行字节码指令。

操作数栈:所有操作都在栈上进行(压栈、出栈、运算)。

栈帧结构:包含命名空间、执行状态、操作数栈,是函数调用的载体。

代码对象:包含字节码、常量池、变量名等编译产物。


📚 推荐教材

《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》


🔗 课程导航

上一讲:字节码编译 | 下一讲:常见字节码指令


💬 联系我

平台账号/链接
微信扫码加好友
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!