在 Python 的执行模型中用于承载“一次具体执行过程”的是另一类运行期对象——帧对象frame object。如果说代码对象描述了“应用如何执行”那么帧对象承载的就是该执行在运行期展开时的具体状态。从对象模型的角度看帧对象是一类专门用于承载执行期状态的对象。它在调用发生时被创建在执行结束后被销毁负责保存局部变量、指令执行位置以及与调用链相关的上下文信息。理解帧对象是理解函数调用、递归、异常传播、作用域解析乃至调试机制的关键。一、什么是帧对象帧对象frame object表示一次正在进行或已经发生的代码执行过程。每当一段代码被执行解释器都会为其创建一个新的帧对象用于记录该次执行所需的全部运行期状态。从概念上看帧对象负责描述• 当前正在执行的是哪一段代码引用代码对象• 局部命名空间与全局命名空间的绑定情况• 当前字节码指令执行到的位置• 与上层调用者之间的调用关系帧对象不是• 代码对象帧对象不描述执行结构• 函数对象帧对象不提供可调用语义• 闭包对象帧对象并不独立保存被闭包捕获的变量在对象模型中帧对象的核心职责只有一个承载“一次执行”的全部运行期状态。二、帧对象的产生从调用到执行上下文帧对象并不是在编译阶段产生的而是在运行期、调用发生时动态创建的。当解释器执行一段可调用对象时大致会经历如下过程1、确定被调用的函数对象2、从函数对象中取得其关联的代码对象3、基于代码对象创建新的帧对象4、初始化帧对象中的运行期状态5、进入字节码解释循环以最简单的函数调用为例def add(a, b): return a b add(1, 2)在执行 add(1, 2) 时• add 对应的代码对象并未发生变化• 解释器创建了一个新的帧对象• 参数 a、b 被写入该帧对象的局部命名空间• 指令指针从代码对象的起始位置开始推进需要强调的是同一个代码对象可以在不同时间、不同调用路径下对应多个不同的帧对象。三、对象协作代码对象、函数对象与帧对象在 Python 的执行模型中这三类对象分工明确、彼此协作• 代码对象描述“如何执行”• 函数对象提供“可调用入口”• 帧对象承载“正在执行”这种分层结构保证了执行模型的可重入性与一致性。1、为什么帧对象必须是独立对象如果将运行期状态直接存放在函数对象或代码对象中将会立即破坏以下性质• 多次调用的相互独立性• 递归调用的正确性• 并发或协程切换时的状态隔离帧对象作为独立的执行对象则可使得• 每一次调用都有独立的执行上下文• 同一函数可被安全地重入调用• 调用链可以被精确地表示和回溯从模型上可以概括为代码对象是“静态蓝本”帧对象是“动态实例”。2、递归与多次调用示例1递归示例def fact(n): if n 1: return 1 return n * fact(n - 1)在该递归过程中• fact 的代码对象始终只有一个• 每一次递归调用都会创建新的帧对象• 各帧对象分别保存各自的 n 值与返回位置正是由于帧对象彼此独立递归调用才能正确展开并回溯。2多次调用示例def inc(x): return x 1 inc(1) # 2inc(10) # 11这里同样体现出• 代码结构被复用• 执行状态不共享四、帧对象的核心运行期字段帧对象内部保存了大量与执行直接相关的信息其中最关键的包括以下部分。1、代码对象引用f_codef_code 指向当前执行的代码对象。它定义了• 字节码指令序列• 局部变量布局• 作用域与闭包结构帧对象并不复制这些信息而是直接引用代码对象。2、局部命名空间f_localsf_locals 保存当前执行上下文中的局部命名空间。def f(): a 1 b 2 return a b在执行过程中该帧的局部命名空间会动态变化。需要注意的是f_locals 是对当前局部变量状态的一种映射表示其更新时机受解释器控制并非实时字典。3、全局命名空间f_globalsf_globals 指向函数或代码对象所属的全局命名空间通常是模块的 __dict__。名称解析LEGB过程中解释器正是通过 f_locals 和 f_globals 这两个映射结构完成查找。4、当前指令位置f_lastif_lasti 表示当前正在执行或即将执行的字节码指令位置。该字段使得• 异常回溯成为可能• 调试器能够暂停与恢复执行• 解释器可以正确处理跳转与返回这也是调试器、追踪器能够“暂停”、“单步执行”的根本依据。5、上一帧引用f_backf_back 指向调用该帧的上一个帧对象形成调用栈链表结构当前帧 → f_back → 调用者帧 → …这一链式结构构成了调用栈call stack。通过 f_back解释器能够• 构建完整的调用栈• 实现异常向上传播• 支持 traceback 机制五、帧对象视角下的作用域与闭包1、帧对象如何承载作用域作用域的结构关系在编译阶段已由代码对象静态确定但具体变量的取值与可见性必须在运行期由执行上下文来承载。在 Python 中这一职责由帧对象在运行期承担。考虑如下示例def outer(x): def inner(y): return x y return inner在执行 outer(10) 时解释器会经历如下过程• 创建 outer 对应的帧对象• 参数 x 被写入该帧的局部命名空间• 由于 x 被内层函数 inner 引用解释器并不将其作为普通局部变量处理相应地x 被提升为 cell 对象承载的自由变量outer 的帧对象不再直接保存 x 的值而是保存对该 cell 的引用。当 inner 被返回并随后调用时• 解释器为 inner 创建新的帧对象• inner 的帧对象并不访问 outer 的帧对象而是通过自身结构中保存的 cell 引用间接读取 x 的值由此可以看出作用域在对象模型中的分工是清晰而分层的• 代码对象声明作用域的结构关系• 帧对象承载一次执行中的局部状态• cell 对象承载可跨帧存续的自由变量值帧对象并不是闭包变量的最终所有者而是闭包语义得以建立的运行期中介。2、帧对象与闭包生命周期在函数返回后其对应的帧对象通常会从调用链中移除并在不再被引用时被销毁。但在闭包场景中这一过程存在一个重要的例外。当帧对象中的某些局部变量被提升为 cell 对象并被闭包函数对象所引用时• 闭包函数对象会持有对 cell 的引用• cell 对象继续保存相关变量的值因此被延长生命周期的并不是帧对象本身而是帧对象中被提取出的 cell 对象。这种设计使得 Python 能够• 精确保留闭包所需的最小运行期状态• 避免无谓地延长整个执行上下文的生命周期• 在执行模型与内存管理之间取得良好平衡正是通过帧对象负责执行、cell 对象负责延续的协作机制Python 将闭包这一语义特性完整地映射进了对象模型之中。六、帧对象的销毁与执行结束当一段代码执行完毕后帧对象从调用栈中弹出。若不再被任何对象引用将被垃圾回收。在以下情况下帧对象可能被暂时保留• 异常尚未被处理用于 traceback• 调试器主动保存执行上下文• 闭包间接引用了其中的 cell这再次体现了帧对象作为“执行期状态载体”的核心定位。 小结帧对象是 Python 执行模型中对“一次具体执行过程”的对象化表达。它在运行期动态创建承载局部状态、执行位置与调用关系并通过与代码对象和函数对象的协作支撑起函数调用、递归、异常传播与闭包等核心机制。通过将执行状态集中于帧对象而将执行结构交由代码对象描述Python 在对象模型层面实现了执行语义的清晰分层。这一设计是 Python 执行模型高度一致性与可组合性的关键基础。“点赞有美意赞赏是鼓励”