Python依赖Frame记录运行状态 过度拆分函数拖累执行性能

【python】python的骨架frame——你写的代码都是运行在它里面的? — 码农高天

码农高天

Python将不可变代码对象与运行时状态解耦至Frame链表中,虽然为调试和栈内省带来完全透明度,但维护调用帧的固有开销会让频繁的微小函数调用沦为性能瓶颈。

AI-assisted summary of 【python】python的骨架frame——你写的代码都是运行在它里面的? — original source. Check the original for full context.

Source published:

Key ideas

  • Python 代码编译生成的 CodeObject 是不可变的静态元信息,而每一次函数调用都会生成独立的 frame 来记录动态的运行时状态与局部变量。 (0:00)
  • Frame 逻辑上构成调用栈,底层通过 f_back 单向链表串联,包含执行指令偏移 f_lasti 以及命名空间字典 f_globals 和 f_locals。 (0:00)
  • 基于 f_trace、f_trace_lines 和 f_trace_opcodes 的回调机制,构成了 PDB 调试器与 Coverage 等覆盖率工具的核心运行基石。 (5:00)
  • Frame 赋予了 Python 极高的运行时透明度,但也导致函数调用带来显著的开销,过度拆分极短函数容易成为性能瓶颈。 (8:30)

Detailed notes

## Background 该视频由技术博主“码农高天”于 2024 年 3 月 30 日发布在 YouTube 平台上。视频简介指出,Python 的所有程序本质上都运行在一个个 frame(帧)之中,作者希望通过本期内容帮助大家深入了解这一鲜为人知却至关重要的 Python 底层运行机制。

## TL;DR 视频深入剖析了 Python 运行时的骨架——frame(帧)的核心工作机制,并明确了其与不可变 CodeObject 的本质区别。Frame 记录了函数调用栈的动态状态,包含局部变量、全局命名空间、指令指针以及用于调试分析的 trace 回调体系。开发者不仅可以利用 frame 实现获取调用者名称、局部变量及源码行号等“黑魔法”反射技巧,也需要认识到其透明度所伴随的函数调用性能开销。

## Atmosphere & style 视频整体呈现出一种沉稳扎实、深入浅出的硬核硬派技术教学风格。高天讲解条理清晰、语调平缓自信,善于运用“程序大楼”等生动比喻将抽象的 CPython 底层结构拆解得通俗易懂。观看过程中既能体会到底层原理被层层剥开的豁然开朗,也能在展示反射“黑魔法”时感受到探索编程底层的极客乐趣与严谨思考。

## Bulletin - [00:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=0s) Python 代码编译生成的 CodeObject 是不可变的静态元信息,而每一次函数调用都会生成独立的 frame 来记录动态的运行时状态与局部变量。 - [00:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=0s) Frame 逻辑上构成调用栈,底层通过 f_back 单向链表串联,包含执行指令偏移 f_lasti 以及命名空间字典 f_globals 和 f_locals。 - [05:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=300s) 基于 f_trace、f_trace_lines 和 f_trace_opcodes 的回调机制,构成了 PDB 调试器与 Coverage 等覆盖率工具的核心运行基石。 - [08:30](https://www.youtube.com/watch?v=7y4iz8Pjews&t=510s) Frame 赋予了 Python 极高的运行时透明度,但也导致函数调用带来显著的开销,过度拆分极短函数容易成为性能瓶颈。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=0s) CodeObject 是编译期生成的不可变对象,仅包含静态元信息,无法记录当前运行时的变量值。 - [00:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=0s) Frame 是运行时的状态容器,每次函数调用都会分配独立的 frame 来维护当前的局部变量与执行上下文。 - [00:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=0s) Python 程序的运行流程类似于调用栈,进入代码块或调用函数即进入新一层 frame,返回时则退回上一层。 - [00:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=0s) 在代码中可以通过 inspect.currentframe() 或 sys._getframe() 获取当前的 frame 对象,推荐使用更规范的 inspect 模块。 - [00:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=0s) Frame 的底层结构通过 f_back 指针连接上一层 frame,形成表示调用关系的单向链表栈。 - [05:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=300s) f_lasti 扮演程序计数器(PC)的角色,记录当前字节码偏移量,保证函数调用返回后能准确回到原处继续执行。 - [05:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=300s) f_lineno 记录当前执行的源码行号,主要用于向开发者提供可视化的定位与排查信息。 - [05:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=300s) 调试器(PDB)和测试覆盖率工具(Coverage)依托 f_trace 及相关行与字节码触发标记实现单步跟踪与回调。 - [05:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=300s) 通过 frame.f_back 可以跨栈层读取调用者的函数名、文件名、行号以及调用者作用域中的局部变量。 - [08:30](https://www.youtube.com/watch?v=7y4iz8Pjews&t=510s) 高度透明的反射机制使开发者能随时掌握完整运行状态,但也导致了函数调用的固有运行时开销。 - [08:30](https://www.youtube.com/watch?v=7y4iz8Pjews&t=510s) 尽管 CPython 尽可能复用 frame 以避免频繁内存分配,但过度将代码拆分成极短函数仍会带来不可忽视的调用瓶颈。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=0s) 运行时状态的容器:Frame 与 CodeObject 的区别

在 Python 底层运行机制中,存在一个非常重要、非常基础但日常开发很少直接打交道的核心概念——frame(中文常被翻译为“帧”,但码农高天直言“我不是特别喜欢这个翻译,所以我们就管它叫 frame”)。在理解 frame 之前,必须先厘清它与 `CodeObject` 的关系。所有 Python 代码在编译期都会先被编译成 `CodeObject`,但 `CodeObject` 本身是不可变(immutable)的对象,编译完成后内容就固定了。虽然运行时需要它,但程序绝不能单纯依赖它,因为程序执行需要记录大量动态的运行时状态。例如,`CodeObject` 记录了某个函数需要使用局部变量 A、B、C,但它根本没有空间去记录当前运行时 A、B、C 的具体数值是多少;“而记录当前运行时状态的责任,就落在了 frame 身上。”

每一个函数只会编译出一个唯一的 `CodeObject`,保存大部分静态元信息;但当函数在运行时被多次调用、递归调用或处于不同上下文中时,每一次调用都会动态生成一个独立的 frame。这个 frame 仅保存该次调用中变化的部分,比如局部变量当前的值。高天形象地打了个比方:“你可以把 Python 的运行时想象成一座大楼,在进入程序的时候从一层开始,然后每有一段新的 code block 就再往上走一层,每一层都记录着当前这一层的状态。”最典型的“换层数”或切换 frame 的场景就是函数的调用与返回。在简化的模型中,每一次函数调用就会新建一个 frame,函数的代码就在此 frame 内执行;一旦调用另一个函数,就进入下一个 frame;当函数返回,就退回上一层 frame。它的本质就是一个栈(call stack)结构。

获取当前的 frame 对象非常简单。在 Python 代码中,通常可以使用 `inspect.currentframe()`,或者使用 `sys._getframe()`。实际上,`inspect.currentframe()` 在成功时调用的就是 `sys._getframe()`,只是两者在获取失败时的行为略有不同。考虑到前缀带下划线的函数属于内部接口,因此更推荐使用 `inspect` 模块封装好的函数。通过类似 `objprint` 的工具打印 frame 对象,会发现其所有属性均以 `f_` 开头,这正好对应了 `CodeObject` 中以 `co_` 开头的属性命名惯例。

```mermaid flowchart TD accTitle: Python 函数调用栈与 Frame 结构 accDescr: 展示函数逐层调用时 Frame 通过 f_back 形成链表栈的引用关系

subgraph S3["Frame 3 (当前正在执行)"] F3["f_code: 目标函数字节码<br/>f_locals: 当前局部变量<br/>f_lasti: 当前指令偏移"] end

subgraph S2["Frame 2 (中间调用者)"] F2["f_code: 中间函数代码<br/>f_locals: 中间局部变量<br/>f_lasti: 等待返回的指令"] end

subgraph S1["Frame 1 (底层的调用入口)"] F1["f_code: 模块/主程序代码<br/>f_globals: 全局命名空间"] end

F3 -->|"f_back"| F2 F2 -->|"f_back"| F1 ```

- `f_back`:指向上一层 frame(即当前函数的调用者对应的 frame)。虽然逻辑上是调用栈,但底层实现上是通过单向链表由 `f_back` 串联起来的。 - `f_builtins`:当前 frame 下对应的内置函数命名空间字典。 - `f_code`:当前 frame 对应的 `CodeObject`,包含编译期生成的关键信息,例如字节码、变量名列表等。 - `f_globals` 与 `f_locals`:当前函数视角下的全局变量字典与局部变量字典。当在函数内执行 `A = 1` 时,Python 正是通过 `f_locals` 记录下了变量 A 变成了 1(虽然底层实现是利用类数组的快速局部变量机制存储,但暴露出来的视图是字典)。

---

[05:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=300s) 指令指针、Trace 机制与 Frame 的“黑魔法”

除了变量字典,frame 中还维护着极其关键的执行控制属性。首先是 `f_lasti`(last instruction),在汇编语言概念中等同于程序计数器(Program Counter)。它记录了当前正在运行哪一个字节码指令。当函数 A 调用函数 B 时,B 返回后 A 能够准确回到原来的位置继续往下跑,依靠的正是 A 自己的 frame 中保存的 `f_lasti`。另一个属性是 `f_lineno`(line number),记录当前正在执行第几行源码,这个属性主要用于面向人类排查和定位代码。

随后的三个属性则构成了 Python 调试和追踪的核心基石:`f_trace`、`f_trace_lines` 和 `f_trace_opcodes`。Python 允许在每运行一行源码或者一个字节码指令后触发一个回调函数。高天解释道:“Python 里的 debugger 就是通过这个机制来完成的,我们比较熟知的 PDB 就是通过它来完成的,包括 Python 的一些工具,比如说 coverage 我们非常常用,也是通过这个机制来完成的。”如果通过 `sys.settrace()` 设置了追踪函数,`f_trace` 就不再为 `None`,而两个布尔值标记则决定是逐行触发(`f_trace_lines`)还是逐个字节码指令触发(`f_trace_opcodes`)。

正因为 frame 的存在,开发者可以在 Python 运行时的任意位置获取整个调用栈以及栈中每一帧的全部上下文状态,这直接催生了许多强大的“黑魔法”技巧: - **查看调用者的名字**:通过当前 frame 获取 `frame.f_back.f_code.co_name`。例如函数 F 被函数 G 调用,在 F 内部就可以直接打印出调用者的名字 `"g"`。 - **窥探调用者的局部变量**:通过 `frame.f_back.f_locals`,即使在被调用的子函数内部,也能实时读取调用者设置的局部变量(例如检测到 G 函数在调用 F 之前设置了 `A = 3, B = 4`)。 - **定位调用发生的源码位置**:针对通用的工具函数(utility function),在不知道是谁从何处调用的情况下,可以通过 `frame.f_back.f_code.co_filename` 和 `frame.f_back.f_lineno`,准确定位被调用的文件名和具体行号(例如准确输出是在该文件的第 11 行发起了调用)。

---

[08:30](https://www.youtube.com/watch?v=7y4iz8Pjews&t=510s) 机制的双刃剑:极高的透明度与函数调用开销

frame 机制赋予了 Python 极高的透明度。开发者可以在程序中的任何一个地方拿到全部的调用栈以及每一个 frame 的所有状态,高天评价说:“也就是说,你几乎可以随时随刻的掌握整个 Python 运行的状态。对于 debug 来说,或者对于某一些喜欢黑魔法的人来说,这绝对是一个好事。”

然而,这种强大的反射与状态监控能力是有代价的。每一个函数调用都要维护 frame,这不可避免地带来了明显的运行时开销(overhead)。尽管在 CPython 的底层实现中做过针对性优化——并不需要每次函数调用都真正地重新 `new` 一个全新的 frame 对象,而是会尽可能复用之前的 frame,但其内部状态管理的成本依然显著。这也是为什么在 Python 中如果将代码拆分成大量过于短小的函数时,函数调用的开销本身往往会成为整个程序最耗时的瓶颈。了解 frame 的机制,不仅有助于掌握 Python 底层的执行流程,也能让人在编写高性能 Python 代码时更有针对性地权衡代码抽象与调用开销。

## Notable quotes > [00:00](https://www.youtube.com/watch?v=7y4iz8Pjews&t=0s) 你可以把 Python 的运行时想象成一座大楼,在进入程序的时候从一层开始,然后每有一段新的 code block 就再往上走一层,每一层都记录着当前这一层的状态。 — 码农高天

> [08:30](https://www.youtube.com/watch?v=7y4iz8Pjews&t=510s) 你几乎可以随时随刻的掌握整个 Python 运行的状态。对于 debug 来说,或者对于某一些喜欢黑魔法的人来说,这绝对是一个好事。 — 码农高天

## Mentioned Python, CPython, Frame, CodeObject, PDB, coverage, inspect, sys, objprint, 码农高天

## Video description 这期视频聊一下frame,也就是帧。python的所有程序其实都是运行在一个个frame里的,只是对于这个底层机制,很多人并不熟悉。希望通过这期视频,大家可以了解一些python底层运行的机制!

[Source](https://www.youtube.com/watch?v=7y4iz8Pjews)

中文简报

Python依赖Frame记录运行状态 过度拆分函数拖累执行性能 Python将不可变代码对象与运行时状态解耦至Frame链表中,虽然为调试和栈内省带来完全透明度,但维护调用帧的固有开销会让频繁的微小函数调用沦为性能瓶颈。 视频深入剖析了 Python 运行时的骨架——frame(帧)的核心工作机制,并明确了其与不可变 CodeObject 的本质区别。Frame 记录了函数调用栈的动态状态,包含局部变量、全局命名空间、指令指针以及用于调试分析的 trace 回调体系。开发者不仅可以利用 frame 实现获取调用者名称、局部变量及源码行号等“黑魔法”反射技巧,也需要认识到其透明度所伴随的函数调用性能开销。 Python 代码编译生成的 CodeObject 是不可变的静态元信息,而每一次函数调用都会生成独立的 frame 来记录动态的运行时状态与局部变量。 (0:00) Frame 逻辑上构成调用栈,底层通过 f_back 单向链表串联,包含执行指令偏移 f_lasti 以及命名空间字典 f_globals 和 f_locals。 (0:00) 基于 f_trace、f_trace_lines 和 f_trace_opcodes 的回调机制,构成了 PDB 调试器与 Coverage 等覆盖率工具的核心运行基石。 (5:00) Frame 赋予了 Python 极高的运行时透明度,但也导致函数调用带来显著的开销,过度拆分极短函数容易成为性能瓶颈。 (8:30)

Reading summary

码农高天指出,Python 代码在运行时的动态状态是由 frame 结构承载的,它与编译期固定的 CodeObject 互为补充并构成了单向链表式调用栈。该机制为调试和运行时内省提供了极高透明度,但也带来了显著的函数调用开销。 Frame 与 CodeObject 的职责切分 (0:00) Python 代码在编译期会先被编译为不可变的 CodeObject,其中保存了字节码和变量名列表等静态元信息,但它并不具备记录具体变量值的空间。记录程序动态运行时状态的职责由 frame 承担。每个函数仅对应一个唯一的 CodeObject,但在多次调用或递归调用时,每一次调用都会动态生成一个独立的 frame,用于存储局部变量当前数值等变化信息。代码中通常可以通过 inspect.currentframe() 或内部接口 sys._getframe() 获取当前 frame。 调用栈模型与核心命名空间属性 (0:00) Frame 在逻辑上表现为调用栈,底层则通过 f_back 属性以单向链表的形式串联。当前函数的 frame 会通过 f_back 指向上层调用者的 frame。Frame 的属性均以 f_ 开头,与 CodeObject 的 co_ 前缀相对应。Frame 内部维护着多个关键命名空间与对象引用:f_code 指向对应的 CodeObject,f_builtins 对应内置函数命名空间,而 f_globals 与 f_locals 则分别提供当前上下文下的全局与局部变量字典视图,例如函数内执行 A = 1 即记录在 f_locals 之中。 执行控制、Trace 机制与运行时内省 (5:00) Frame 维护着控制代码流转的核心字段,其中 f_lasti 充当程序计数器角色,记录当前字节码指令偏移,确保函数调用返回后能回到正确位置;f_lineno 则记录当前源码行号。此外,f_trace、f_trace_lines 和 f_trace_opcodes 构成了 PDB 调试器与 coverage 工具的基石,允许开发者在每行源码或每条字节码执行后触发回调。借助这些机制,程序可以在被调函数内部逆向读取调用者信息,例如通过 frame.f_back.f_code.co_name 获取调用者函数名、通过 frame.f_back.f_locals 读取调用方局部变量,或通过 frame.f_back.f_code.co_filename 与 frame.f_back.f_lineno 精确定位调用的源码文件与行号。 状态透明度的性能代价 (8:30) Frame 机制赋予了 Python 极高的透明度,使开发者能在任何位置掌握调用栈各层的完整执行状态,利于排错和高级反射技巧。但这种动态追踪能力伴随着固定的运行时开销。尽管 CPython 底层对 frame 进行了复用优化,避免每次调用都重新分配全新对象,其状态维护成本仍然显著。高天强调,若在 Python 中过度拆分极短小的函数,函数调用的开销本身容易成为性能瓶颈,编写高性能代码时需在抽象度与调用成本之间权衡。 深入理解 frame 的存储与执行追踪机制,既能帮助开发者掌握 Python 运行时的底层全貌与反射技巧,也为评估小函数拆分对执行性能的影响提供了直接依据。

Source quotes

[0:00] 码农高天: 你可以把 Python 的运行时想象成一座大楼,在进入程序的时候从一层开始,然后每有一段新的 code block 就再往上走一层,每一层都记录着当前这一层的状态。 [8:30] 码农高天: 你几乎可以随时随刻的掌握整个 Python 运行的状态。对于 debug 来说,或者对于某一些喜欢黑魔法的人来说,这绝对是一个好事。

Original transcript

[0:00] 这期视频我们来介绍一个在Python运行的时候非常重要非常基础但是我们其实不太会接触到的东西就是free那frame这个东西呢中文被翻成真啊我不是特别喜欢这个翻译所以我们就管它叫frame可能有人还记得我在蛮久之前做过一期有关CodeObject的视频在那期视频里我告诉大家所有你写的python代码都会在编译期先被编译成CodeObject但是code object这个东西它是一个immutable他编译一次就完成了所以在运行排档代码的时候我们会使用到它但是不能单纯的依赖它因为我们有非常多的运行时的状态需要保存比如说在code object里面我们记录了某一个函数需要用到什么local variable比如这个函数需要用到ABC这三个local variable但是code object里面没有地方记录现在abc分别是多少而记录当前运行时状态的责任就落在了frame身上那frame和code object区别在于每一个函数只会编译出来一个code object它大部分的信息是保存在code object里面的然而你在运行的时候一个函数可能会被重复掉用它可能有地规可能有各种各样的情况每一次掉用会出现一个frame这个frame里面只保存每一次调用的时候这个函数不同的东西比如说当前local variable的值是什么你可以把python的运行石想象成一座大楼在进入程序的时候从一层开始然后没有一段新的code block就再往上走一层每一层都记录着当前这一层的状态那么我们最常见的换层数或者说换frame的状况就是函数的调用与返回在刚开始,为了简单起见,你可以认为每一个函数调用就会新建一个frame然后这个函数的代码就都在这个frame里面运行直到这个函数调用了另一个函数,他就要去下一个frame,或者这个函数返回,他就返回上一个frame它是一个stack也就是站的结构那在python代码里面我们是可以直接拿到当前的frame这个object的一般来说我们是使用这个inspect.currentframe当然我们也可以用这个sys.getframe事实上这个CurrentFrame在成功的时候调用的就是这个GetFrame只是他们两个在失败时候的behavior不一样我们知道前面加下滑线的函数一般是internal usage所以尽管这个下滑线getframe是可以用的我还是更推荐大家使用这个inspect里面的函数我们可以通过Object Print把这个frame给打出来可以看到这个frame attribute里面都是f下滑线开头的如果你还记得的话在code object里面所有的 attributes都是CO小线开头的我们来把这些属性逐一的跟大家介绍一下首先这个Fback是它上一个frame也就是调用函数的函数他所对应的frame那刚才我说这个frame本质是一个站结构它的实现是用链表来完成的每一个frame知道调用它的frame是什么然后通过FBAC连接起来Fbuiltins是这个frame下对应的builtins函数也就是所谓的内置函数f code就是我们之前讲到过的那个code object了还记不记得我们需要用到很多code object里面的内容才知道程序应该怎么运行code object里面有编译期我们拿到的跟这个函数有关的事最重要的 比如他的字节码 变量的名字 这些东西Global和下面的locals其实都比较好理解就是这个函数眼里的全局变量和局部变量这个locals里面记录了我当前这个frame里面所有的局部变量的值我在一个函数里面写完A等于1他怎么知道A变成1了呢就是因为通过这个Flocal这个frame记住了A变成1了当然在之前的一期视频里我们也提到了对吧这个FLocals [5:00] 其实是一个读出来的值那它里面真正的机制是用了一个类似数组的方式去保存的这些local variable只是机制上你完全可以这么理解F last I 就是 last instruction,你可以把它理解为Program Counter如果你学过汇编的话他的意思就是我现在运行到哪一个字节码了一个函数怎么知道自己运行到哪儿下一个应该运行什么了呢就是通过这个last instruction比如我一个函数A掉用函数B函数B返回的时候A怎么知道它原来运行到哪了呢就是通过 last instruction在A的frame里面他记录了这个last instruction是多少所以在B函数返回的时候他可以继续运行Line number 其实这个更多的是给人类看的就是我现在运行到第几行了我们看左边我们打印的这一行代码是在第6号所以现在line number是6下面这三件事呢 理解难度就会稍微大一些因为他们是跟trace有关的python允许你在每运行一行代码或者是一个字节码之后去trigger一个callback function就每运行一行程序就调用一下你写的函数那python里的debugger就是通过这个机制来完成的我们比较熟知的PDB就是通过它来完成的包括拍放的一些工具比如说coverage我们非常常用也是通过这个机制来完成的当ftrace是none的时候他就不会调用任何函数而当你通过sys.setTrace设置了一个trace函数的时候这个F trades就不是难了那下面两个boolean trace lines跟这个trace upcodes就是说你是每一行trigger一下这个函数 还是每一个字节码trigger一下这个函数那frame的存在就相当于我们可以在python的任何一个位置获取到整个的调用站以及这个调用站里面每一帧的全部情况这就让很多黑魔法成为了可能举个例子我想知道调用我的这个函数它叫什么名字我们看我们就可以在拿到了当前这个frame之后找到这个frame的f-back然后拿到它的F-code打印它这个F-code里面的codename当这个F函数是被G函数调用的时候可以看到它打印出来了一个G还比如说我想知道调用我的这个函数它的局部变量是什么样的我就可以用frame.fback.flocals我们看在G里面运行F之前我先做了一个A等于3又做了一个B等于4在运行这个程序的时候这个F就知道调用我的那个函数当前的frame里面a13 b14又比如说我想知道调用我的这个函数是在什么地方调用的我有的时候你可能有一个 utility function这个function在很多地方被调用过那你想知道某一个状态下是谁调用的它我们也可以用frame来完成这个事我们可以用这个fcode.cofile name来拿到调用它的这个函数所在的文件然后用fback.flinembers来拿到当前情况下它在哪一行可以看到运行的时候他会告诉你是在这个文件下当前是在第11行我们看左边的程序确实是在第11行调用的F函数那python这个frame的机制实际上是让python的调用结构变得非常非常清晰的而且你可以在程序中任何一个地方拿到全部的调用站以及每一个frame的所有状态也就是说你几乎可以随时随刻的掌握整个python运行的状态对于debug来说或者对于某一些喜欢黑魔法的人来说这绝对是一个好事当然从另一个角度讲这个frame的机制也增加了一定的python函数调用的overhead尽管对于frame这个地方 cpython是有做特别的优化的并不用每一次函数调用真的需要去new一个新的frame object他会重复利用之前的frame但是这个机制依然有着比较明显的overhead所以经常有的时候你会发现当你写一些比较小比较短的函数的时候函数调用反而成了整个程序里面最耗时的地方好那今天对frame的介绍就到这里希望大家有所帮助如果喜欢的话记得点一下关注别忘了一键三连哦音乐 [10:00] you