CPython 运行时架构
理解 CPython 的内部工作原理,是写出高效 Python 代码的基础。
CPython 是什么
Python 有多种实现:CPython(官方)、PyPy、Jython、MicroPython。我们日常用的 python 命令就是 CPython,用 C 语言编写。
你的 .py 文件
↓ 词法分析 (tokenize)
Token 流
↓ 语法分析 (ast)
AST 抽象语法树
↓ 编译 (compile)
字节码 .pyc
↓ 执行 (ceval.c)
CPython 虚拟机
↓
结果核心组件
1. 解释器主循环 ceval.c
CPython 的核心是 Python/ceval.c 中的 _PyEval_EvalFrameDefault 函数,一个巨大的 switch-case 循环,逐条执行字节码指令。
python
import dis
def add(a, b):
return a + b
dis.dis(add)
# 输出:
# 2 0 RESUME 0
# 3 2 LOAD_FAST 0 (a)
# 4 LOAD_FAST 1 (b)
# 6 BINARY_OP 0 (+)
# 10 RETURN_VALUE2. 帧对象 PyFrameObject
每次函数调用都会创建一个帧(Frame),包含:
| 字段 | 说明 |
|---|---|
f_code | 代码对象(字节码、常量、变量名) |
f_locals | 局部变量字典 |
f_globals | 全局变量字典 |
f_back | 上一帧(调用栈) |
f_lasti | 上一条执行的字节码偏移 |
python
import sys
def show_frame():
frame = sys._getframe()
print(f"函数名: {frame.f_code.co_name}")
print(f"文件名: {frame.f_code.co_filename}")
print(f"局部变量: {frame.f_locals}")
show_frame()3. 代码对象 PyCodeObject
函数编译后的产物,不可变,可被多个帧共享:
python
def greet(name):
msg = f"Hello, {name}!"
return msg
code = greet.__code__
print(code.co_varnames) # ('name', 'msg') — 局部变量名
print(code.co_consts) # (None,) — 常量
print(code.co_argcount) # 1 — 参数数量
print(code.co_stacksize) # 虚拟机栈深度执行流程详解
源码 → 字节码
python
import ast, py_compile, marshal
source = "x = 1 + 2"
# 1. 生成 AST
tree = ast.parse(source)
print(ast.dump(tree, indent=2))
# 2. 编译为代码对象
code = compile(source, "<string>", "exec")
# 3. 查看字节码
import dis
dis.dis(code)模块导入机制
import numpy
↓
sys.modules 缓存检查
↓ (未命中)
sys.meta_path 查找器列表
↓
PathFinder → sys.path 路径搜索
↓
找到 numpy/__init__.py
↓
执行模块代码,写入 sys.modules['numpy']python
import sys
# 查看已加载模块
print(list(sys.modules.keys())[:10])
# 查看模块搜索路径
print(sys.path)
# 查看元路径查找器
print(sys.meta_path)内存分配器
CPython 有三层内存分配:
Python 对象层 (pymalloc)
↓
CPython 内存池 (arena → pool → block)
↓
系统 malloc / free小对象优化(< 512 bytes):CPython 使用自己的内存池 pymalloc,避免频繁调用系统 malloc,大幅提升小对象分配性能。
引用计数 + 垃圾回收
python
import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # 2(a 本身 + getrefcount 参数)
b = a
print(sys.getrefcount(a)) # 3
del b
print(sys.getrefcount(a)) # 2循环引用问题:
python
import gc
# 创建循环引用
a = {}
b = {}
a['b'] = b
b['a'] = a
del a, b
# 引用计数无法回收,需要 gc 模块的分代回收器
gc.collect() # 手动触发版本演进关键节点
| 版本 | 关键特性 |
|---|---|
| 3.10 | 结构模式匹配 match/case |
| 3.11 | 解释器速度提升 25%,更好的错误信息 |
| 3.12 | 每解释器独立 GIL(sub-interpreters) |
| 3.13 | 实验性 无 GIL 模式(--disable-gil) |
| 3.14 | 进一步优化 JIT 编译器(实验性) |
实践建议
用 dis 模块查看你写的函数的字节码,能帮助你理解 Python 的执行开销,写出更高效的代码。
注意
CPython 的内部 API(sys._getframe() 等)不保证跨版本稳定,生产代码慎用。
延伸阅读
- CPython 源码 —
Python/ceval.c是核心 - Inside the Python Virtual Machine
dis模块文档 → 下一篇:字节码与 dis 模块