Python 3.11 的 PyFrameObject 更轻量,通过重构生命周期与字段布局实现按需分配:基础帧仅保留核心字段(约160字节),异常、调试、生成器等非常规字段延迟动态附加,显著降低高频小函数调用的内存与初始化开销。

PyFrameObject 在 Python 3.11 中变轻了,不是靠“省一点内存”凑出来的,而是重构了整个生命周期——绝大多数函数调用根本不需要完整帧结构,自然就快了。
Frame对象在3.10和3.11中的内存布局差异
Python 每次函数调用都会创建一个 PyFrameObject,它保存局部变量、指令指针、异常状态等。3.10 中这个结构是“全量预分配”的:哪怕你函数里只有一行 return 42,也会为 f_back、f_trace、f_gen、f_exc_info 等所有可能字段预留空间(约 240 字节)。3.11 改成按需扩展:
• 基础帧只保留最核心字段:f_code、f_localsplus、f_lasti 等,大小压到约 160 字节
• 非常规字段(如调试钩子、异常上下文、生成器状态)仅在首次真正需要时动态附加
• 这种“懒分配 + 结构体切片”设计,让普通函数跳过冗余字段的初始化开销
哪些操作会让3.11的Frame又变重?
一旦触发以下任一行为,该帧就会补全扩展区,体积回归接近 3.10 的水平:
- 进入
try块(哪怕没抛异常,只要字节码含SETUP_FINALLY) - 设置
sys.settrace()或函数内调用breakpoint() - 函数是生成器(含
yield)或协程(async def)——f_gen字段必存 - 手动访问
frame.f_back或frame.f_trace属性(触发 lazy-init)
这不是 bug,而是设计权衡:解释器优先保障无副作用场景的极致轻量,有调试/异常/协程需求时再按需加载。
为什么 sys._getframe() 看不出优化效果?
sys._getframe() 会强制触发帧的“完全初始化”,因为它要确保你能安全读取所有字段(比如 f_back),所以会把所有可选字段都补全。真实受益场景是高频小函数调用链:
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
立即学习“Python免费学习笔记(深入)”;
- 递归深度大的函数(如
fibonacci)——每层少分配 80+ 字节,万级深度省下近 1MB - 大量短生命周期的
lambda或嵌套函数——几乎从不触发异常或调试,帧始终保持最小形态 - FastAPI/Starlette 路由中每个请求产生的中间调用链——调用栈深但单帧轻量,整体堆压力下降明显
Frame轻量化对函数调用开销的实际影响
减少的不只是内存,更是初始化成本:
- 分配更小结构体 → 更快的内存申请(尤其在
mmap或 slab 分配器上) - 复制更少字段 → 函数返回时帧销毁更快
- 缓存行更紧凑 → 更高 CPU 缓存命中率,尤其在循环密集调用中
- GC 压力降低 → 更少的追踪对象、更短的扫描时间
注意:这种优化对单次调用感知微弱,但在调用频次高(>10⁵/秒)、帧生命周期短(

















