mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
704 字
2 分钟
Python 解释器原理:CPython 执行流程
2023-05-15

凌晨两点,线上服务突然内存飙升。你打开监控,发现 Python 进程的 RSS 已经是启动时的三倍,但业务日志里没有任何异常。tracemalloc 报告显示对象数量在持续增长——你意识到这不是业务逻辑的问题,而是 Python 内存管理的”黑箱”在作祟。要真正理解这个问题,必须回到起点:Python 代码到底是怎么被执行的?

CPython 执行流程概览#

flowchart TB A[Python 源码] --> B[词法分析 Lexer] B --> C[Token 流] C --> D[语法分析 Parser] D --> E[AST 抽象语法树] E --> F[编译器 Compiler] F --> G[字节码 Bytecode] G --> H[Python 虚拟机] H --> I[执行结果]

一、词法分析#

1.1 Token 类型#

Python 将源码分解为 Token(词法单元)

# 源码
x = 1 + 2
# Token 流
NAME 'x' (1, 0)
EQUAL '=' (1, 2)
NUMBER '1' (1, 4)
PLUS '+' (1, 6)
NUMBER '2' (1, 8)
NEWLINE '' (1, 9)

主要 Token 类型:

类型示例
NAMEx, print, if
NUMBER123, 3.14, 0xff
STRING’hello’, “world”
KEYWORDif, else, for, while
OP+, -, *, /, =
NEWLINE\n
INDENT缩进
DEDENT减少缩进

1.2 词法分析过程#

// Python 词法分析器(简化)
static int tok_get(tok_state *tok, char **p_start, char **p_end) {
char c;
// 跳过空白和注释
while (true) {
c = tok_nextc(tok);
if (c == ' ' || c == '\t') continue;
if (c == '#') {
while (c != '\n') c = tok_nextc(tok);
}
break;
}
// 识别标识符
if (isalpha(c) || c == '_') {
return NAME;
}
// 识别数字
if (isdigit(c)) {
return NUMBER;
}
// 识别字符串
if (c == '"' || c == '\'') {
return STRING;
}
// ...
}

二、语法分析#

2.1 AST 结构#

Python 使用 AST(Abstract Syntax Tree,抽象语法树) 表示程序结构:

# 源码
def add(a, b):
return a + b
result = add(1, 2)
# AST(简化表示)
Module(body=[
FunctionDef(
name='add',
args=arguments(args=[
arg(arg='a'),
arg(arg='b')
]),
body=[
Return(value=BinOp(
left=Name(id='a'),
op=Add(),
right=Name(id='b')
))
]
),
Assign(
targets=[Name(id='result')],
value=Call(
func=Name(id='add'),
args=[Num(n=1), Num(n=2)]
)
)
])

2.2 查看 AST#

import ast
code = """
def add(a, b):
return a + b
"""
tree = ast.parse(code)
print(ast.dump(tree, indent=2))

2.3 AST 节点类型#

flowchart TB A[AST Node] --> B[语句 stmt] A --> C[表达式 expr] A --> D[其他] B --> B1[FunctionDef] B --> B2[ClassDef] B --> B3[Return] B --> B4[Assign] B --> B5[If] B --> B6[For] B --> B7[While] C --> C1[BinOp] C --> C2[Compare] C --> C3[Call] C --> C4[Name] C --> C5[Num] C --> C6[Str] D --> D1[arguments] D --> D2[arg] D --> D3[keyword]

三、编译阶段#

3.1 字节码#

Python 字节码是栈式虚拟机指令:

# 源码
def add(a, b):
return a + b
# 字节码
import dis
dis.dis(add)
2 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 RETURN_VALUE

3.2 常用字节码指令#

指令操作
LOAD_CONST加载常量到栈
LOAD_FAST加载局部变量到栈
LOAD_NAME加载名字到栈
STORE_FAST存储到局部变量
BINARY_ADD弹出两个值,相加,结果入栈
BINARY_SUBTRACT弹出两个值,相减
BINARY_MULTIPLY弹出两个值,相乘
COMPARE_OP比较操作
POP_JUMP_IF_FALSE条件跳转
JUMP_FORWARD无条件跳转
CALL_FUNCTION调用函数
RETURN_VALUE返回栈顶值

3.3 编译过程#

flowchart TB A[AST] --> B[符号表构建] B --> C[作用域分析] C --> D[字节码生成] D --> E[优化] E --> F[Code Object] subgraph 符号表 B1[局部变量] B2[自由变量] B3[全局变量] B4[内置变量] end
// 编译 AST 到字节码(简化)
static int compiler_visit_expr(compiler *c, expr_ty e) {
switch (e->kind) {
case BinOp_kind:
// 编译左操作数
compiler_visit_expr(c, e->v.BinOp.left);
// 编译右操作数
compiler_visit_expr(c, e->v.BinOp.right);
// 生成操作指令
compiler_addop(c, binop_opcode(e->v.BinOp.op));
break;
case Name_kind:
// 加载变量
compiler_addop_name(c, LOAD_NAME, e->v.Name.id);
break;
// ...
}
}

3.4 Code Object#

def func(x):
y = x + 1
return y
# 查看 Code Object
print(func.__code__.co_code) # 字节码
print(func.__code__.co_varnames) # 局部变量名
print(func.__code__.co_consts) # 常量
print(func.__code__.co_names) # 名称
print(func.__code__.co_filename) # 文件名
print(func.__code__.co_firstlineno) # 起始行号

四、Python 虚拟机#

4.1 执行模型#

Python 虚拟机是栈式虚拟机:

flowchart LR subgraph 栈帧 Frame A[数据栈<br/>Value Stack] B[块栈<br/>Block Stack] C[局部变量<br/>Locals] end D[字节码] --> A A --> E[执行结果]

4.2 栈帧结构#

// PyFrameObject 结构(简化)
typedef struct _PyInterpreterFrame {
PyObject *f_func; // 函数对象
PyObject *f_locals; // 局部变量
PyObject *f_globals; // 全局变量
PyObject *f_builtins; // 内置函数
PyCodeObject *f_code; // 代码对象
int f_lasti; // 上条指令位置
// 数据栈
PyObject **stackpointer;
PyObject **stacktop;
// 块栈(用于异常、循环)
PyTryBlock f_blockstack[CO_MAXBLOCKS];
} PyInterpreterFrame;

4.3 执行循环#

// Python 虚拟机主循环(简化)
PyObject* _PyEval_EvalFrameDefault(PyThreadState *tstate,
PyInterpreterFrame *frame) {
PyObject **stack_pointer = frame->stacktop;
// 主执行循环
for (;;) {
// 获取指令
_Py_CODEUNIT *next_instr = frame->instr_ptr++;
unsigned char opcode = _Py_OPCODE(*next_instr);
switch (opcode) {
case LOAD_CONST: {
PyObject *value = GETITEM(frame->code->co_consts, oparg);
Py_INCREF(value);
PUSH(value);
break;
}
case LOAD_FAST: {
PyObject *value = GETLOCAL(oparg);
if (value == NULL) {
// 未初始化变量错误
}
Py_INCREF(value);
PUSH(value);
break;
}
case BINARY_ADD: {
PyObject *right = POP();
PyObject *left = TOP();
PyObject *sum = PyNumber_Add(left, right);
SET_TOP(sum);
Py_DECREF(left);
Py_DECREF(right);
break;
}
case RETURN_VALUE: {
PyObject *value = POP();
return value;
}
// ... 更多指令处理
}
}
}

4.4 栈操作示例#

# 代码
x = 1 + 2
# 字节码执行过程
# 1. LOAD_CONST 1
# 栈: [1]
# 2. LOAD_CONST 2
# 栈: [1, 2]
# 3. BINARY_ADD
# 弹出 1, 2,计算 1+2=3,压入 3
# 栈: [3]
# 4. STORE_NAME 'x'
# 弹出 3,存入 x
# 栈: []
sequenceDiagram participant VM as 虚拟机 participant Stack as 数据栈 participant Locals as 局部变量 VM->>Stack: LOAD_CONST 1 Note over Stack: [1] VM->>Stack: LOAD_CONST 2 Note over Stack: [1, 2] VM->>Stack: BINARY_ADD Note over Stack: [3] VM->>Locals: STORE_NAME x Note over Locals: x=3 Note over Stack: []

五、内存管理#

5.1 内存分配层次#

flowchart TB A[Python 对象] --> B[PyMalloc<br/>小对象分配器] B --> C[系统内存池<br/>Arena/Pool] C --> D[操作系统<br/>malloc/free] E[大对象<br/>>512B] --> D

5.2 PyMalloc#

Python 使用自己的内存分配器管理小对象:

// 内存池结构
struct pool_header {
union { block *_padding; } pad;
block *freeblock; // 空闲块链表
struct pool_header *nextpool;
struct pool_header *prevpool;
uint arenaindex; // Arena 索引
uint szidx; // 块大小索引
uint refcount; // 已分配块数
};
struct arena_object {
uptr address; // Arena 起始地址
block* pool_address; // Pool 数组
uint nfreepools; // 空闲 Pool 数
uint ntotalpools; // 总 Pool 数
};

内存分配策略:

对象大小分配方式
< 512BPyMalloc 从 Pool 分配
>= 512B直接 malloc

5.3 垃圾回收#

Python 使用 引用计数(Reference Counting) + 循环垃圾回收:

// 引用计数
#define Py_INCREF(op) ((void)(++(op)->ob_refcnt))
#define Py_DECREF(op) \
do { \
if (--((op)->ob_refcnt) == 0) \
_Py_Dealloc((PyObject *)(op)); \
} while (0)
flowchart TB A[对象创建] --> B[引用计数 = 1] B --> C{引用变化} C -->|引用增加| D[计数 + 1] C -->|引用减少| E[计数 - 1] D --> C E --> F{计数 = 0?} F -->|是| G[立即释放] F -->|否| C H[循环引用] --> I[引用计数无法回收] I --> J[循环 GC 标记清除]

循环垃圾回收:

import gc
# 开启自动 GC
gc.enable()
# 手动触发 GC
gc.collect()
# 查看 GC 状态
gc.get_stats()

六、对象系统#

6.1 PyObject 结构#

所有 Python 对象的基础:

// PyObject 结构
typedef struct _object {
Py_ssize_t ob_refcnt; // 引用计数
PyTypeObject *ob_type; // 类型指针
} PyObject;
// 变长对象
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; // 元素个数
} PyVarObject;

6.2 类型对象#

// PyTypeObject 结构(简化)
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; // 类型名
Py_ssize_t tp_basicsize; // 基本大小
// 方法
destructor tp_dealloc; // 析构函数
printfunc tp_print; // 打印函数
getattrfunc tp_getattro; // 属性获取
setattrfunc tp_setattro; // 属性设置
// 数字方法
binaryfunc nb_add;
binaryfunc nb_subtract;
binaryfunc nb_multiply;
// 序列方法
lenfunc sq_length;
binaryfunc sq_concat;
ssizeargfunc sq_item;
// 映射方法
lenfunc mp_length;
binaryfunc mp_subscript;
// ...
} PyTypeObject;

6.3 对象创建#

// 创建整数对象
PyObject *PyLong_FromLong(long v) {
PyLongObject *result;
// 小整数缓存
if (-5 <= v && v <= 256) {
return PyLong_FromSmallInt(v);
}
// 分配并初始化
result = PyObject_MALLOC(sizeof(PyLongObject));
if (!result) return NULL;
result->ob_base.ob_refcnt = 1;
result->ob_base.ob_type = &PyLong_Type;
result->ob_digit[0] = v;
return (PyObject *)result;
}

七、函数与闭包#

7.1 函数对象#

def add(a, b):
return a + b
# 函数对象属性
print(add.__code__) # 代码对象
print(add.__globals__) # 全局变量
print(add.__defaults__) # 默认参数
print(add.__closure__) # 闭包变量

7.2 闭包实现#

def outer(x):
def inner(y):
return x + y
return inner
func = outer(10)
func(5) # 返回 15
# 闭包字节码
dis.dis(func)
2 0 LOAD_DEREF 0 (x)
2 LOAD_FAST 0 (y)
4 BINARY_ADD
6 RETURN_VALUE

闭包数据结构:

// 闭包单元
typedef struct {
PyObject_HEAD
PyObject *ob_ref; // 捕获的变量
} PyCellObject;
// 函数对象
typedef struct {
PyObject_HEAD
PyCodeObject *func_code; // 代码对象
PyObject *func_globals; // 全局变量
PyObject *func_defaults; // 默认参数
PyObject *func_kwdefaults; // 关键字默认参数
PyObject *func_closure; // 闭包单元元组
} PyFunctionObject;

八、性能优化#

8.1 字节码缓存#

# Python 会缓存编译后的字节码
# .py 文件 -> .pyc 文件
# 缓存位置
__pycache__/
module.cpython-311.pyc

8.2 小整数缓存#

# 小整数 (-5, 256) 被缓存
a = 256
b = 256
print(a is b) # True
a = 257
b = 257
print(a is b) # False (新对象)

8.3 字符串驻留#

# 字符串自动驻留
a = "hello"
b = "hello"
print(a is b) # True
a = "hello world"
b = "hello world"
print(a is b) # 可能 False (包含空格)

8.4 使用 dis 模块分析#

import dis
# 分析函数字节码
def example():
x = [i * 2 for i in range(10)]
return x
dis.dis(example)
# 查看字节码详细信息
dis.Bytecode(example).info()

九、性能调试#

9.1 cProfile 分析#

import cProfile
def slow_function():
total = 0
for i in range(100000):
total += i
return total
cProfile.run('slow_function()', sort='cumulative')

9.2 sys.monitoring (Python 3.12+)#

import sys.monitoring as monitor
# 设置监控
monitor.use_tool_id(monitor.PROFILER_ID, "my_profiler")
# 注册事件处理器
@monitor.register_callback
def on_call(code, offset):
print(f"Called {code.co_name} at offset {offset}")

9.3 内存分析#

import sys
# 查看对象大小
x = [i for i in range(1000)]
print(sys.getsizeof(x)) # 列表容器大小
# 使用 tracemalloc 追踪内存
import tracemalloc
tracemalloc.start()
# ... 代码 ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
print(stat)

关键要点#

  1. 词法分析:源码转化为 Token 流
  2. 语法分析:Token 流转化为 AST(Abstract Syntax Tree,抽象语法树)
  3. 编译:AST 转化为字节码
  4. 执行:字节码由栈式虚拟机执行
  5. 内存管理引用计数(Reference Counting) + 循环 GC
  6. 对象系统:PyObject 统一接口

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

Python 解释器原理:CPython 执行流程
https://blog.souloss.com/posts/principles/principles-python-interpreter/
作者
Souloss
发布于
2023-05-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时