Skip to content

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_VALUE

2. 帧对象 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() 等)不保证跨版本稳定,生产代码慎用。

延伸阅读

本站内容由 褚成志 整理编写,仅供学习参考