码农高天
Python 3.13 的 No-GIL 与 JIT 编译器因性能损耗严重且缺乏提速尚不能用于生产,本次更新的核心价值在于 PEP 667 确立的 locals 快照语义与对历史调试器缺陷的底层修复。
AI-assisted summary of 聊聊3.13中你们感兴趣的特性,和我实现的功能 — original source. Check the original for full context.
Source published:
Key ideas
- Python 3.13 引入实验性 Free-Threaded 模式(python3.13t),非生产可用,当前单线程性能损耗约 30%,未来目标是将损耗控制在 10% 以内。 (0:00)
- 3.13 的 JIT 编译器仍需从源码编译且性能提升为 0%,但其兼容性优于 No-GIL,预计在 3.14 或 3.15 推出正式开箱即用版本。 (0:00)
- 官方移植了 PyPy 的交互式解释器,为 REPL 带来自动缩进、多行代码块跨行编辑、彩色 Traceback 以及无括号 quit 退出功能。 (3:00)
- 高天亲自实现 PEP 667,使 locals() 明确为独立的只读快照,并引入可实时双向同步局部变量的 Frame.f_locals 代理对象以强化调试器机制。 (5:00)
- PDB 修复了长达十余年的作用域末行 breakpoint() 漂移 Bug,同时消除了内置命令与同名变量/表达式的解析冲突。 (9:30)
Detailed notes
## Background 该视频由 YouTube 频道“码农高天”于 2024 年 10 月 11 日发布。视频围绕 Python 3.13 正式版的更新展开,作者在简介中表示将聚焦万众期待的 No-GIL(Free-Threaded)与 JIT、全新的交互式 REPL,以及由他本人亲手实现的 PEP 667 和多项 PDB 调试器核心特性。
## TL;DR 高天详细剖析了 Python 3.13 的几项重磅与关键改动。他指出 No-GIL 仍处于单线程性能慢约 30% 的实验阶段且需使用 python3.13t 运行,JIT 亦无性能提升且需源码编译,提醒社区切勿盲目用于生产;相比之下,移植自 PyPy 的新 REPL 带来了多行编辑与彩色报错,显著提升了交互体验。随后,高天以核心开发者的第一视角拆解了他实现的 PEP 667,通过快照机制与 Frame locals 代理终结了 locals() 混乱的历史行为,并修复了 PDB 困扰开发者十余年的代码块末行断点与命令命名冲突等顽疾。
## Atmosphere & style 视频兼具硬核的技术底色与极其亲切幽默的开发者“吐槽”风格,像是在听一位深耕底层的工程师好友复盘刚刚上线的新版 Python。高天从容揭开官方 Release 浮华宣传背后的真实性能现状,在聊到十多年来断点机制的荒唐设计与赶在 Beta 阶段紧急救火的经历时妙语连珠、快意犀利。整期内容既有底层机制的精密剖析,又洋溢着亲身参与并改进全球主流语言后的自豪与从容。
## Bulletin - [00:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=0s) Python 3.13 引入实验性 Free-Threaded 模式(python3.13t),非生产可用,当前单线程性能损耗约 30%,未来目标是将损耗控制在 10% 以内。 - [00:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=0s) 3.13 的 JIT 编译器仍需从源码编译且性能提升为 0%,但其兼容性优于 No-GIL,预计在 3.14 或 3.15 推出正式开箱即用版本。 - [03:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=180s) 官方移植了 PyPy 的交互式解释器,为 REPL 带来自动缩进、多行代码块跨行编辑、彩色 Traceback 以及无括号 quit 退出功能。 - [05:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=300s) 高天亲自实现 PEP 667,使 locals() 明确为独立的只读快照,并引入可实时双向同步局部变量的 Frame.f_locals 代理对象以强化调试器机制。 - [09:30](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=570s) PDB 修复了长达十余年的作用域末行 breakpoint() 漂移 Bug,同时消除了内置命令与同名变量/表达式的解析冲突。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=0s) Free-Threaded Mode(去 GIL)目前仅作为独立的实验性程序(python3.13t)存在,严禁用于生产环境。 - [00:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=0s) 当前 No-GIL 版本的单线程运行速度仅为原版的 70% 左右,多线程的并发开销导致多核扩展性并不理想。 - [00:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=0s) 3.13 官方安装包未集成 JIT 编译器,体验必须手动源码编译,且目前的性能提升幅度为 0%。 - [03:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=180s) 全新的 REPL 移植自 PyPy,虽然合并较晚存在部分边缘 Bug,但大幅提升了交互编辑体验。 - [03:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=180s) 新 REPL 支持回车自动缩进、跨行按上箭头自由移动编辑,并允许通过上箭头召回整个多行代码块。 - [03:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=180s) Traceback 报错信息首次支持彩色高亮显示,且报错位置指向更加精确,退出 REPL 允许直接输入 quit。 - [05:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=300s) Python 3.12 之前的 locals() 内部复用同一个字典对象,且每次调用都会被局部 fast variable 强制覆盖,语义极其混乱。 - [05:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=300s) PEP 667 规定 locals() 每次调用均生成独立的局部变量快照字典,彼此解耦且修改快照不会反向影响局部变量。 - [09:30](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=570s) PEP 667 核心引入了 Frame.f_locals 代理对象,实现了对函数局部 fast variable 的实时双向读写同步。 - [09:30](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=570s) 彻底修复了断点位于代码块末行时自动跳出作用域的十余年历史 Bug,开发者在 except 块末尾打断点无需再额外塞入 pass。 - [09:30](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=570s) 增强了 PDB 解释器的语法分析能力,解决了命令前缀(如 list、q)与用户变量及表达式之间的命名冲突问题。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=0s) Python 3.13 重点特性:实验性 Free-Threaded (No-GIL) 与 JIT 编译器
Python 3.13 正式版发布,官方的 release highlights 列出了三大改动。高天首先聚焦于呼声已久的后两项特性。第一个是基于 PEP 703 的 Free-Threaded Mode(即去 GIL / No-GIL)。高天坦言自己此前没想到 3.13 就会推出实验版本,原以为至少要等到 3.14 才会有一个可用的版本。他强调,网上对这个特性的解读五花八门,但大家必须认清其现状:“首先我们要知道这个FreeThreaded Mode是一个实验性版本,你可以把它理解为和正式版并列的另外一个程序。”如果用户正常下载安装 Python 3.13 并运行 `python3.13`,它依然是带有 GIL 的传统版本;只有在 Windows 和 macOS 安装包里勾选相应选项,去专门运行名为 `python3.13t` 的可执行文件时,才是没有 GIL 的版本。
由于这只是实验版本,它绝对不是 production-ready 的,“换言之不要用它跑任何正式的上线程序,这个东西就是给你试着玩的。未来什么时候会production ready不知道,他会不会替代现有的有GIL的版本不知道,这一切都取决于他的开发和社区对他的接受程度,所以大家先不用太兴奋啊,觉得Python马上就没有GIL了。”在性能方面,单线程程序在 No-GIL 版本下的运行速度大约只有原版本的 70% 左右(即慢了约 30%)。未来若想彻底替代有 GIL 的版本,除了稳定性与兼容性,单线程性能损耗必须压到 10% 以内。网上有些展示四线程获得两倍加速的测试,其根本原因就在于单线程变慢了,加上并发开销(overhead),多线程扩展性无法达到线性的 3 到 4 倍。
另一个被念叨了很久的特性是 JIT 编译器(JIT Compiler)。在 3.13 中,JIT 距离普通用户甚至比 Free-Threaded 更远,因为官方安装包完全没有集成 JIT,想要体验就必须自行从源码编译(compile from source)。不过,因为 JIT 目前的实现机制兼容性更好,它距离正式发布的距离反而比 Free-Threaded 更近,预计在 3.14 或最多 3.15 就能看到自带 JIT 的正式版。至于当前实验版 JIT 的加速效果,高天直言提升幅度是 0%,“就是这个JIT并没有让Python快一丁点”,仅作为目前的开发进度展示,期待未来有所突破。
- **3.13t 可执行文件**:Free-Threaded 模式作为并列的独立实验程序存在,命令行为 `python3.13t`,非线上生产可用。 - **单线程性能损耗**:No-GIL 版本单线程较当前慢约 30%(仅达到原版 70% 速度);未来目标是将性能损耗压缩至 10% 以内。 - **JIT 现状与预期**:3.13 中必须源码编译(compile from source),当前性能提升为 0%,但预计 3.14 或 3.15 可进入正式可用阶段。
---
[03:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=180s) 全新交互式解释器(REPL)体验与 Traceback 彩色化
距离普通开发者最近、改动最显著的是全新的交互式解释器(Interactive Interpreter / REPL)。这个新 REPL 实际上是把 PyPy 的 REPL 直接拿过来使用的。由于它几乎是在 Beta 阶段前一天才合入主分支,因此成为了 3.13 的“Bug 大户”,虽然正式发布时已经修复了极多问题,但可能仍残留部分边缘 Bug。
在实际使用体验上,新 REPL 带来了现代化升级。在 Python 3.12 之前,在 REPL 中定义函数敲回车后光标仍处于顶格位置,必须手动缩进打空格;更痛苦的是写完后如果想给函数增加参数,根本无法直接按上箭头修改上一行,按上箭头只会调出上一行的历史输入,修改只能推倒重来。而在 Python 3.13 中,回车会自动缩进,直接按上箭头可以无缝跳回上一行对应位置编辑;定义完整个函数代码块后,按上箭头调出的是刚刚定义的整段代码块,即支持了多行编辑(Multi-Line Editing)。此外,REPL 和报错 Traceback 第一次引入了颜色显示(目前 REPL 内尚未支持源码语法高亮,但 Traceback 已经有颜色,且报错指向的信息点更准),退出时也可以直接输入 `quit` 而无需输入括号 `quit()`。“Python这个古老的语言啊,终于开始逐渐拥抱一点点现代化的设施了”,整个 REPL 的体验得到了质的提升。
```mermaid flowchart TD accTitle: REPL 在 Python 3.12 与 3.13 的操作差异 accDescr: 对比 Python 3.12 与 3.13 在交互式解释器中多行代码输入与修改的逻辑流
subgraph Legacy["Python 3.12 (旧 REPL)"] direction TB A1["输入函数声明回车"] --> B1["光标顶格,需手动空格缩进"] B1 --> C1["按上箭头试图改上一行"] C1 --> D1["只调出单行历史记录,无法跨行编辑,必须重写"] end
subgraph Modern["Python 3.13 (PyPy 移植新 REPL)"] direction TB A2["输入函数声明回车"] --> B2["自动缩进对齐"] B2 --> C2["按上箭头"] C2 --> D2["光标直接回到上一行对应字符,支持修改"] D2 --> E2["支持 Multi-Line Editing,上箭头可召回整个 Block"] end ```
- **多行编辑(Multi-Line Editing)**:支持在代码块内自由上下移动光标修改逻辑,上箭头可召回整个代码块。 - **自动缩进与交互优化**:回车自动对齐缩进,退出终端支持直接输入 `quit`(省略函数调用括号)。 - **Traceback 现代化**:终端 Traceback 加入颜色高亮,报错定位的位置信息更加精准。
---
[05:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=300s) PEP 667:高天亲手重构的 locals() 语义与快照机制
Release highlights 中有一句让很多开发者乃至中文官方文档读者都一头雾水的说明:“`locals()` built-in now has defined semantics for changing the return mapping.”高天笑称:“这句话大家应该看不懂对吧,根本就不知道他说的什么意思……为什么要说一下呢?因为这个东西是我做的,我写的。这个是 PEP 667 的一部分。”
在 3.13 之前,`locals()` 的底层行为极其混乱、充满陷阱。在函数内部,Python 局部变量并非保存在字典中,而是作为优化过的 fast variable 存储。旧版本运行 `locals()` 时,系统会将 fast variable 打包填入一个内部共用的字典并返回;每次调用 `locals()` 都会更新这个同一个字典。这导致了匪夷所思的现象: 1. 若函数内定义了 `k = 0`,则 `k` 是局部 fast variable;如果写 `d = locals()`、修改 `d['k'] = 2` 后再次执行 `d2 = locals()`,由于更新机制生效,`d['k']` 会被局部变量原值 `0` 覆盖,且 `d is d2` 返回 `True`。 2. 若函数内没有 `k = 0`,修改 `d['k'] = 2` 并在后续执行 `d2 = locals()` 时,由于 `k` 不是 fast variable,字典不会被局部变量覆盖,`d['k']` 依然是 `2`。 3. 一旦开启调试器(`sys.settrace`),在退出 debugger 时 Python 还会强制把 locals 字典的值写回给 fast variable,造成难以理清的状态混乱。
PEP 667 在这种混沌中建立了确定的秩序。在 Python 3.13 中,每次调用 `locals()` 返回的都是当前局部变量的一份不可变累赘的快照(snapshot)。它只是一个普通的字典,与后续的执行完全解耦。在 3.13 下,即使多次调用 `locals()`,每次都会生成一个全新独立的字典(`d is not d2`),对快照字典的修改不会再被后续的 `locals()` 覆盖,更不会暗中改变真正的局部变量。
```mermaid flowchart LR accTitle: Python 3.12 与 3.13 中 locals() 行为对比 accDescr: 展示 PEP 667 前后 locals() 返回值从共享字典覆盖转变为独立快照的机制
subgraph py312["Python 3.12 旧行为"] F1["Fast Variables (k=0)"] -->|"同步更新"| D1["共享 locals 字典"] D1 -.->|"每次调用返回同一个对象 (d is d2)"| D1 D1 -->|"用户修改 d['k']=2"| M1["d['k'] 变为 2"] M1 -->|"再次调用 locals()"| O1["被 Fast Variable 强行覆盖回 0"] end
subgraph py313["Python 3.13 PEP 667 新行为"] F2["Fast Variables (k=0)"] -->|"拍照"| S1["Snapshot 字典 d (k=0)"] S1 -->|"修改 d['k']=2"| S1M["d['k'] 保持为 2"] F2 -->|"再次调用 locals()"| S2["生成全新独立 Snapshot d2 (d is not d2)"] S2 -.->|"与快照 d 毫无关联,互不影响"| S1M end ```
- **历史包袱**:旧版局部变量以 fast variable 存储,`locals()` 内部复用并刷新全局唯一的字典对象,造成诡异的“变量被意外覆盖”现象。 - **快照语义(Snapshot)**:PEP 667 规范了语义,在 3.13 中每次调用 `locals()` 仅获取当时局部状态的一个快照字典,返回的新字典相互独立。
---
[09:30](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=570s) Frame locals proxy 与 PDB 核心断点体验修复
高天指出,`locals()` 返回快照只是 PEP 667 的表面表现,该提案真正的核心是“如何直接、可靠地修改局部变量”。为了解决调试器变量同步的顽疾,3.13 引入了 `Frame.f_locals` 代理对象(proxy)。通过该 proxy,用户既能立即读取局部变量的变动,也能对 proxy 赋值并瞬间同步到实际的局部变量中。高天在现场代码演示中,函数定义了 `k = 0`,通过 `f_locals['k'] = 1`,局部变量 `k` 立即变成 `1`;修改局部变量 `k = 2`,读取 `f_locals['k']` 也同步变为 `2`。两者实现了完全实时的双向同步,彻底解决了调试器中变量修改脱节的问题。高天提到,这项功能也是他在 Beta 阶段前花了一两周时间紧急赶制完成的。
随后高天分享了他在 3.13 中对 PDB(Python Debugger)的诸多改进。虽然 Release 页面只列了几条,但他做出的改动多到无法逐一列举,其中他最满意的有两项:
第一项改动修复了困扰 Python 开发者十余年的断点位置 Bug。在 3.12 及更早版本中,如果在 `try...except` 块的最后一行写 `breakpoint()`,程序暂停时实际上停在退出异常处理块之后的下一行(例如紧随其后的 `return 0`)。此时已经退出了 `except` 作用域,开发者在调试器中根本拿不到、也打印不出刚刚捕获的异常对象。高天对此犀利吐槽:“这是因为Python 3.12的breakpoint并不是停在breakpoint这一行,而是停在下一行要运行的代码之前。这就导致了如果你的breakpoint是你这个code block的最后一行,你是没有办法停到这个code block里面的。这件事非常之蠢,而且蠢了十多年。”在 3.13 中,这一问题被彻底修复,断点会精确停在 `breakpoint()` 这一行,开发者无需再为了调试而在断点下方习惯性地硬塞一行毫无意义的 `pass`。
第二项改动解决了 PDB 内置命令与变量/表达式的命名冲突问题。在旧版 PDB 中,输入表达式如果前缀恰好是 PDB 内置命令(如 `list` 或退出命令 `q`),调试器会无脑将其作为命令进行解析,导致试图构造列表(如通过某些表达式调用)或者在变量 `q` 上执行 `.append()` 时直接报错甚至误退调试器。在 3.13 中,PDB 增强了对 Python 语句和表达式的识别与解析能力,即使变量名与命令重合也能正确执行对应的代码操作。高天表示,Python 3.13 中的 PDB 相比前两个版本易用性大幅提升,而 Python 3.14 还会带来更多有趣的 PDB 改动。
- **`Frame.f_locals` 实时代理(Proxy)**:提供了双向直通底层 fast variable 的通道,读写实时同步,极大稳固了调试器的底层机制。 - **作用域末行断点修复**:彻底终结了代码块末尾 `breakpoint()` 停到作用域外的历史 Bug,在 `except` 块内打断点可正常检查捕获的异常对象,不再依赖辅助的 `pass`。 - **命令与表达式冲突解决**:修复了输入包含 `list`、`q` 等前缀的代码被误判为 PDB 指令的问题,恢复了正常执行 Python 语句的能力。
## Notable quotes > [00:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=0s) FreeThreaded Mode是一个实验性版本,你可以把它理解为和正式版并列的另外一个程序。 — 高天
> [03:00](https://www.youtube.com/watch?v=GFWs3G1W7BE&t=180s) Python这个古老的语言啊,终于开始逐渐拥抱一点点现代化的设施了 — 高天
## Mentioned Python 3.13, Python 3.12, Python 3.14, PEP 703, PEP 667, Free-Threaded Mode, GIL, JIT, PyPy, REPL, PDB, Frame.f_locals, 高天
## Video description 3.13正式发布啦,我们来聊几个新功能。其中包括万众期待的no-gil和JIT,新的REPL,还有我实现的PEP 667以及几个pdb的feature。
[Source](https://www.youtube.com/watch?v=GFWs3G1W7BE)