在 Python 的执行模型中,用于承载'一次具体执行过程'的,是另一类运行期对象——帧对象(frame object)。如果说代码对象描述了'应用如何执行',那么帧对象承载的就是该执行在运行期展开时的具体状态。
从对象模型的角度看,帧对象是一类专门用于承载执行期状态的对象。它在调用发生时被创建,在执行结束后被销毁,负责保存局部变量、指令执行位置以及与调用链相关的上下文信息。理解帧对象,是理解函数调用、递归、异常传播、作用域解析乃至调试机制的关键。
一、什么是帧对象
帧对象(frame object)表示一次正在进行或已经发生的代码执行过程。每当一段代码被执行,解释器都会为其创建一个新的帧对象,用于记录该次执行所需的全部运行期状态。
从概念上看,帧对象负责描述:
- 当前正在执行的是哪一段代码(引用代码对象)
- 局部命名空间与全局命名空间的绑定情况
- 当前字节码指令执行到的位置
- 与上层调用者之间的调用关系
帧对象不是:
- 代码对象(帧对象不描述执行结构)
- 函数对象(帧对象不提供可调用语义)
- 闭包对象(帧对象并不独立保存被闭包捕获的变量)
在对象模型中,帧对象的核心职责只有一个:承载'一次执行'的全部运行期状态。
二、帧对象的产生:从调用到执行上下文
帧对象并不是在编译阶段产生的,而是在运行期、调用发生时动态创建的。
当解释器执行一段可调用对象时,大致会经历如下过程:
- 确定被调用的函数对象
- 从函数对象中取得其关联的代码对象
- 基于代码对象创建新的帧对象
- 初始化帧对象中的运行期状态
- 进入字节码解释循环
以最简单的函数调用为例:
def add(a, b):
return a + b
add(1, 2)
在执行 add(1, 2) 时:
add对应的代码对象并未发生变化- 解释器创建了一个新的帧对象
- 参数
a、b被写入该帧对象的局部命名空间 - 指令指针从代码对象的起始位置开始推进
需要强调的是,同一个代码对象,可以在不同时间、不同调用路径下,对应多个不同的帧对象。
三、对象协作:代码对象、函数对象与帧对象
在 Python 的执行模型中,这三类对象分工明确、彼此协作:
- 代码对象:描述'如何执行'
- 函数对象:提供'可调用入口'
- 帧对象:承载'正在执行'
这种分层结构保证了执行模型的可重入性与一致性。
1. 为什么帧对象必须是独立对象
如果将运行期状态直接存放在函数对象或代码对象中,将会立即破坏以下性质:
- 多次调用的相互独立性
- 递归调用的正确性
- 并发或协程切换时的状态隔离
帧对象作为独立的执行对象,则可使得:
- 每一次调用都有独立的执行上下文
- 同一函数可被安全地重入调用
- 调用链可以被精确地表示和回溯
从模型上可以概括为:代码对象是'静态蓝本',帧对象是'动态实例'。
2. 递归与多次调用示例
(1)递归示例
def ():
n <= :
n * fact(n - )


