
CPython 内部实现文档全解析从解析器、编译器到解释器、GC 与 JIT 的源码导读【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpythonCPython即本仓库所实现的 Python 官方参考解释器的InternalDocs/目录汇集了面向 CPython 核心维护者maintainer与深度贡献者的内部实现文档它并不讲解如何用 Python 编程而是解释 CPython 解释器自身如何工作。本文以 InternalDocs/README.md 的索引为骨架逐一梳理这些内部文档的定位与核心内容并结合仓库中的源码布局帮助你建立一张从源码字符流到字节码执行再到运行时内存管理的完整认知地图并为后续深入阅读相应源码文件提供精确入口。定位与阅读须知这不是语言规范在深入每个主题之前先理解这套文档的边界。InternalDocs/README.md 开篇就给出两条关键声明受众是 CPython 维护者文档描述的是 CPython 的实现细节implementation details而非 Python 语言规范language specification的组成部分细节随时可变这些实现细节可以在任意两个 CPython 版本之间变化也不应假设在其他 Python 实现如 PyPy、Jython中成立。阅读以最新版本源码为准本仓库当前文档所描述的实现即对应仓库内代码的状态。核心开发团队会尽力保持文档与代码同步若发现过时之处README 建议通过 CPython 的 issue tracker 反馈。这也意味着当你阅读其他介绍 CPython 3.11/3.12 旧版内部机制的博客时与本仓库更新版本的文档发生出入是正常现象——例如下文会提到的栈引用_PyStackRefQSBR按实际 C 栈深度做栈保护等都是较新版本才引入的机制。这套文档按主题分为六大板块覆盖编译Compiling、运行时对象Runtime Objects、程序执行Program Execution、内建类型Built-in Types与模块Modules等板块文档一句话主题通用资源structure.md源码目录布局与各类对象的标准文件摆放约定编译parser.mdPEG 解析器原理与生成器工作方式编译compiler.md从源码到字节码的编译流水线编译changing_grammar.md修改 Python 语法时涉及的全部改动清单运行时对象code_objects.md编译产物CodeObject的结构与元数据运行时对象generators.md生成器与协程的底层实现运行时对象frames.md函数调用的激活记录Frame布局与分配程序执行interpreter.md字节码解释器主循环与指令分派程序执行stackrefs.md求值栈上的标记化引用_PyStackRef程序执行jit.md基于 trace 录制的 JIT第二层优化程序执行garbage_collector.md引用计数 分代 GC 的设计程序执行exception_handling.md零成本异常处理机制程序执行qsbr.md自由线程构建中的内存回收QSBR程序执行stack_protection.md针对 C 栈溢出的RecursionError防护内建类型string_interning.md字符串驻留单例与动态驻留内建类型Objects/listsort.txt列表排序算法详解保留在原始位置模块asyncio.mdasyncio C 实现的内部细节从源码结构建立第一印象structure.md 是整个源码树的地图回答了初入 CPython 仓库最常见的困惑我想找某个东西它应该在哪个文件文档给出了一套典型的布局约定纯 Python 模块Lib/module.py若同时存在 C 加速模块则放在Modules/_module.c配套测试Lib/test/test_module.py文档Doc/library/module.rst扩展模块Modules/modulemodule.cC 实现在Modules/下不以下划线命名内建类型Objects/builtinobject.c例如int对应 Objects/longobject.c、str对应 Objects/unicodeobject.c内建函数Python/bltinmodule.c文档见 Doc/library/functions.rst少数例外内建模块sys位于 Python/sysmodule.cmarshal位于 Python/marshal.cWindows 专用模块winreg位于 PC/winreg.c。这套约定是后续所有源码级阅读的基础看到Lib/asyncio/下的 Python 实现与 Modules/_asynciomodule.c 并存就能立刻理解纯 Python 参考实现 C 加速器这一经典结构。编译流水线Python 源码如何变成字节码PEG 解析器有序选择与无歧义parser.md 介绍了解析器的现状自 PEP 617 起CPython 用PEGParsing Expression Grammar解析器取代了最初的 LL(1) 解析器由 Grammar/python.gram 语法定义文件经解析器生成器生成 Parser/parser.c。日常修改语言语法时开发者改的是语法文件而非生成器本身。与上下文无关文法相比PEG 的核心差异是选择运算符有序规则rule: A | B | C会按书写顺序依次尝试取第一个成功者。由此产生两个重要推论PEG 的选择不可交换把 A、B 换序可能改变解析结果PEG 文法无歧义字符串若可解析则解析树唯一。PEG 解析器通常以递归下降方式实现每个语法规则对应一个解析函数规则体就是函数体。每个解析函数的结果只有两类——成功可选择性消费输入或失败不消费任何输入。值得强调的是PEG 中失败是正常的控制流由于选择有序一次失败往往只是试试下一个分支的信号。文档还提示读者CPython 的 PEG 解析器对输入流的处理相对特殊且需要理解回溯与cut等机制才能完全掌握。从词法到字节码的五步流水线compiler.md 给出了编译的核心流程共五步词法分析Tokenize把源码切成 token 流代码位于 Parser/lexer/ 与 Parser/tokenizer/语法分析Parse把 token 流解析为抽象语法树AST入口在 Parser/parser.cAST → 指令序列把 AST 转换为中间指令序列代码在 Python/compile.c构建控制流图并优化构造 CFGControl Flow Graph并应用优化代码在 Python/flowgraph.c生成字节码基于 CFG 发射字节码代码在 Python/assemble.c。文档还澄清了本仓库版本 PEG 解析器的一个非常规设计其输入是token 流而非一般 PEG 解析器常见的字符流。字面量 token冒号、数字等的定义集中在 Grammar/Tokens并由它生成若干 C 文件。AST 的定义采用Zephyr ASDLAbstract Syntax Definition Language描述规范文件为 Parser/Python.asdl。AST 是对程序结构的高层抽象不依赖具体源码文本。从源码结构看由 ASDL 定义生成的Python/Python-ast.c与Include/internal/pycore_ast.h构成了编译前端与后端之间的数据结构契约。修改语法一份必须逐项勾选的清单changing_grammar.md 提醒所有想给 Python加新语法的人改语法绝不止编辑python.gram一件事。文档给出的清单精确到每一步要改哪个文件、跑哪条命令改动对象涉及文件再生成命令语法规则与 AST 构建动作Grammar/python.grammake regen-pegenWindows 下build.bat --regen由 Tools/peg_generator 生成 Parser/parser.c新增 token 类型Grammar/Tokensmake regen-token重新生成Include/internal/pycore_token.h、Parser/token.c、Lib/token.py 与Doc/library/token-list.incAST 节点定义Parser/Python.asdlmake regen-ast重新生成Include/internal/pycore_ast.h与 Python/Python-ast.c新注释/字符串字面量Parser/lexer/词法代码本身—AST 校验Python/ast.c—AST 反解析unparse供 PEP 563 注解转字符串Python/ast_unparse.c—AST 编译compiler 模块见 compiler.mdast模块的 Python 反解析器_Unparserin Lib/ast.py—AST 文档Doc/library/ast.rst—语法测试Lib/test/test_grammar.py追加新语法用例—高层模块映射库模块pyclbr、Lib/tokenize.py 等—操作上有两个实用提示其一若同时改了python.gram和Tokens必须先跑make regen-token再跑make regen-pegenWindows 的build.bat --regen会一次性完成两者其二当莫名其妙不工作时先试试make clean——因为很多改动依赖再生成派生文件缓存往往导致假象。运行时对象CodeObject、Frame 与 GeneratorCodeObject字节码及其元数据的容器code_objects.md 定义的CodeObject是表示已编译可执行体如编译后的函数或类的内建类型携带一串字节码指令及其执行所需的元数据常量值、上下文信息如源码位置供调试器使用等。该文档披露了几个对理解较新版本解释器至关重要的实现细节自 3.11 起PyCodeObjectC 结构体的最后一个字段是不定长数组code-co_code_adaptive直接内嵌字节码。旧版本中是独立的bytes对象co_code改成内嵌数组是为了省一次分配并允许其在运行时被改写供内联缓存与特化使用。CodeObject 通常由字节码编译器产出但经常由一个进程写到磁盘、另一个进程读回。磁盘形态经marshal协议序列化创建 CodeObject 时Python/specialize.c 中的_PyCode_Quicken()会被调用来初始化所有自适应指令的缓存因为磁盘格式是裸字节序列部分缓存须以 16 位值初始化。CodeObject名义上不可变但部分字段含co_code_adaptive以及_co_monitoring这类运行时信息字段可变可变字段不参与哈希与比较。源码位置信息的组织方式同样有讲究co_linetable表面上叫行号表实际为每条指令记录一个4 元组源位置起止行列号数据量可观因此必须采用紧凑格式。异常发生时解释器向 traceback 追加条目tb_lineno惰性计算自tb_lasti最后执行的指令配合位置表由 C API 函数PyCode_Addr2Line等查询。Python 侧则可通过codeobject.co_positions()这类便捷方法获取。Frame一次函数调用的完整现场frames.md 把一次 Python 函数调用称为一个激活记录frame它包含三个概念区段局部变量含参数、cell 与自由变量求值栈运算中间值Specials虚拟机所需的逐帧对象引用包括 globals 字典、code object、指令指针、栈深度、前驱 frame 等。结构体_PyInterpreterFrame定义于Include/internal/pycore_interpframe_structs.h。由于 Python 语义允许 frame 比其 C 调用活得久如生成器挂起后仍保留现场frame 不能简单分配在 C 调用栈上。为降低开销并改善引用局部性大多数 frame 被连续分配在每线程栈上见Python/pystate.c的_PyThreadState_PushFrame而生成器/协程的 frame 则内嵌在生成器对象中不走线程栈。当前版本采用的布局是 Specials → Locals → Stackspecials 大小固定因此 locals 偏移量编译期可知解释器只需维护 frame 指针与栈指针两个指针。文档还回顾了 3.11 alpha 期间实验过的另一种布局Locals → Specials → Stack调用时参数无需搬移但 VM 需多维护一个 locals 指针供读者理解布局取舍背后的性能权衡。Generator挂在 yield 上的挂起与恢复generators.md 说明 CPython 用PyGenObject结构实现生成器其本质是一个 frame 关于执行状态的元数据。每次调用生成器的send()即在它的 frame 中恢复执行这类似于调用普通函数时在其 frame 中执行——区别在于普通函数只向调用方 frame 返回一次而生成器每yield一次就归还执行权。机制上YIELD_VALUE字节码与RETURN_VALUE相似把值压栈并把执行归还调用 frame但它还需多做两件事以便日后恢复更新 frame 的指令指针并把解释器的异常状态保存在生成器对象上恢复时再把这些异常状态拷回解释器。值得注意的内存布局生成器 frame 直接内嵌在生成器对象中见Include/internal/pycore_interpframe_structs.h中的_PyGenObject_HEAD因此可以从生成器拿到 frame也能反向从 frame 拿到生成器_PyGen_GetGeneratorFromFrame。生成器函数的字节码以RETURN_GENERATOR指令开头它创建含内嵌 frame 的生成器对象先用当前执行中的 frame 初始化这个内嵌 frame再把owner字段改写为由生成器持有随后压栈并返回调用方。之后每次由 Objects/genobject.c 中的gen_send_ex2()恢复时会调用_PyEval_EvalFrame()在生成器内嵌 frame 中继续执行。程序执行解释器主循环与运行时机制字节码解释器一条主循环 一张大 switchinterpreter.md 描述解释器的工作方式其入口在 Python/ceval.c。宏观上解释器是一条遍历字节码指令的循环每条指令经 switch 语句的一个 case 执行。值得强调的是这个巨大的 switch 并非手写而是从 Python/bytecodes.c 中的指令定义用专为该目的设计的 DSL 编写DSL 说明见 Tools/cases_generator/interpreter_definition.md生成的。当 C APIPyEval_EvalCode()执行一个 CodeObject 时会构造 frame 并调用_PyEval_EvalFrame()在其中执行。执行环境还包括线程状态对象tstate携带异常状态、递归深度由它可访问解释器状态tstate-interp乃至真正全局的 runtime 状态tstate-interp-runtime。_PyEval_EvalFrame()的throwflag参数指示是否直接抛出现有异常服务于gen.throw。默认情况下它调用_PyEval_EvalFrameDefault()但按 PEP 523 可通过interp-eval_frame替换。指令解码的物理基础是字节码存储为16 位 code unit 数组_Py_CODEUNIT每个 unit 含 8 位opcode与 8 位oparg为与机器字节序解耦opcode恒为首字节、oparg恒为次字节用宏_Py_OPCODE(word)/_Py_OPARG(word)提取。解释器文档还覆盖指令缓存、特化specialization、异常表、栈行为与如适用GIL 与自由线程等主题是理解Python 为何这样执行的第一手材料。栈引用_PyStackRef求值栈上的标记化值stackrefs.md 介绍解释器求值栈上值的表示形式_PyStackRef——一个带标签的指针宽度值见Include/internal/pycore_stackref.h通过标签位携带所有权元数据并支撑诸如小整数直存的优化Py_TAG_REFCNT未设置引用计数位于所指对象上Py_TAG_REFCNT已设置所有权为借用关闭时无需递减 refcount或对象为 immortalPy_INT_TAG已设置小整数直接存于 stackref 本体无堆分配。另有特殊常量PyStackRef_NULL、PyStackRef_ERROR以及内嵌的None/True/False。在 GIL 构建下大多数对象携带 refcount标记为借用的引用在关闭时跳过 decref在自由线程构建中标签还用来标记延迟引用计数的对象使 GC 可见并避免在共享对象上产生 refcount 竞争。从PyObject*到_PyStackRef有三种控制所有权的转换PyStackRef_FromPyObjectNew(obj)新建引用若对象会消亡则 INCREF、PyStackRef_FromPyObjectSteal(obj)接管所有权而不改动计数除非对象 immortal、PyStackRef_FromPyObjectBorrow(obj)借用关闭时永不 decref。反向转回PyObject*操作与之镜像对应。自适应解释器与 JIT从单指令优化到跨指令优化CPython 的性能分层在 jit.md 与 interpreter 文档中被说得很清楚默认的自适应解释器adaptive interpreter在单条指令粒度做运行时优化含指令特化 specialization通过内联缓存 inline cache 记录执行历史JIT则基于把整段字节码指令序列替换掉的机制从而可以做跨越多条指令的优化。历史上自适应解释器被称为tier 1、JIT 被称为tier 2这一称呼的痕迹仍残留在代码与注释中如backoff_counter_triggers等。JIT 构建存在两个解释器默认的自适应解释器与 trace 录制解释器。程序先在自适应解释器上运行直到某个JUMP_BACKWARD或RESUME指令依据内联缓存中的计数器判断该处足够热超过阈值见 Include/internal/pycore_backoff.h随即调用 Python/optimizer.c 中的_PyJit_TryInitializeTracing并通过ENTER_TRACING()宏切入trace 模式。在支持 computed goto 与尾调用的平台上会直接换掉分派表其余平台则在 opcode 里用一个标志位控制。录制阶段会在每条解释指令的DISPATCH()之后跳转到TRACE_RECORD指令记录刚执行过的指令及其后继所需的活动值据此把上一指令翻译进 trace最终用优化后的 executor 取代热路径上的指令序列。InternalDocs/下关于自由线程与栈保护的文档都与这套执行框架协同演进。垃圾回收引用计数为主、分代 GC 兜底循环引用garbage_collector.md 首先澄清一个高频误区CPython 的主要 GC 算法是引用计数。核心思想是CPython 统计有多少地方引用某个对象另一对象、全局/静态 C 变量、某 C 函数的局部变量皆可构成引用引用计数归零即释放若对象含指向其他对象的引用则递归递减其计数。可用sys.getrefcount()观察注意返回值恒比直觉大 1因为调用时函数本身也持有一份引用 x object() sys.getrefcount(x) 2 y x sys.getrefcount(x) 3 del y sys.getrefcount(x) 2引用计数的致命弱点是无法处理引用环文档给出了经典自引用容器示例列表指向自身del后计数不归零 container [] container.append(container) sys.getrefcount(container) 3 del container因此 CPython 在引用计数之上叠加了跟踪容器对象环的分代垃圾收集器。文档其余部分深入介绍 GC 的代际结构、哪些对象需要被跟踪、如何检测环、如何运行收集、以及与weakref、__del__、gc模块Modules/gcmodule.c的交互等是排查内存泄漏与理解对象生命周期的基础读物。异常处理零成本设计如何做到exception_handling.md 指出 CPython 采用零成本zero-cost异常处理在不抛异常的主流路径上支持异常的成本被压到零或接近零抛出异常的成本有所上升但不多。实现上异常处理信息异常表不与指令流交织而是编码在专门的结构中程序正常执行时完全不必触碰它仅在异常发生时才根据异常表查找对应处理逻辑。文档以一段try/except编译为例展示其如何被翻译为SETUP_FINALLY、PUSH_EXC_INFO、POP_BLOCK等指令序列并给出异常表exception table的格式定义供需要解析字节码的读者对照。它还解释了清理cleanup、finally、with等结构的展开方式以及解释器如何在展开栈时定位 handler。自由线程与内存回收QSBRqsbr.md 面向 Python 的自由线程free-threaded构建。实现无锁数据结构时的关键难题是何时可以安全释放已从结构中逻辑移除的内存——过早释放造成 use-after-free过晚释放则内存膨胀。安全内存回收SMR方案通过把 free 推迟到所有并发读访问必然已结束来化解矛盾QSBRQuiescent-State Based Reclamation正是 Python 自由线程构建用于管理共享内存生命周期的 SMR 方案。QSBR 要求线程周期性报告自己处于静止状态quiescent state——即不持有任何可能被回收的共享对象引用类似于线程打一个checkpoint声明自己不在任何依赖共享资源的操作中途。在 Python 中eval_breaker为线程报告该状态提供了自然且便利的时机。为什么需要它虽然 CPython 内存管理以引用计数 跟踪 GC 为主但并非所有数据结构都适用——例如 list 对象的底层数组并不单独参与引用计数却可能比外层PyListObject活得更短若等下一次 GC 再回收又太迟。这类场景正是 QSBR 的用武之地。文档还说明线程静止状态的追踪方式、批量延迟回收机制等并指出它与引用计数、GC 机制在自由线程构建中如何分工。栈保护把崩溃变成 RecursionErrorstack_protection.md 讲解 CPython 如何把失控或过深的递归导致的 C 栈溢出转化为可捕获的RecursionError而不是让进程直接崩溃。对纯 Python 递归的栈防护早已存在3.12 起增加了对C 代码中栈溢出的防护——起初用计数器实现3.14 改进为使用实际栈深度。在支持查询栈边界的平台Windows、macOS 及大多数 Linux上CPython 直接向操作系统查询栈边界其余平台则使用保守估计。文档以 ASCII 图描述 C 栈布局栈顶端之下依次是soft limit软上限与hard limit硬上限软/硬上限之间及硬上限之下各预留了_PyOS_STACK_MARGIN_BYTES的余量。解释器在 C 调用尤其是递归过程中监测当前栈指针与上限的距离一旦越过软上限即触发RecursionError路径从而防止真正触底崩溃。Python 侧可借由sys.setrecursionlimit()/sys.getrecursionlimit()调节递归深度上限但需知晓其与底层栈余量保护是两层不同的机制。内建类型层面的优化字符串驻留与列表排序String interning让is等于string_interning.md 描述*驻留字符串interned strings*机制概念上驻留字符串属于解释器全局的驻留串集合满足两条不变量——同一解释器内不存在内容相同而不同的两个驻留字符串因此两个驻留字符串可安全地用指针相等Python 的is比较内容是否相同。该机制主要用于加速字典查找与属性查找等场景。CPython 用两种机制实现驻留单例singletons与动态驻留dynamic interning。单例256 个可能的单字符 latin-1 字符串以静态数组形式存放可通过_Py_LATIN1_CHR(c)取用位于_PyRuntime.static_objects.singletons.strings.ascii与...latin1。更长的单例字符串在 C 源码中用_Py_ID字符串是合法 C 标识符片段时或_Py_STR需单独 C 兼容名时标记同样存于静态数组它们由make regen-global-objectsTools/build/generate_global_objects.py从 CPython 源码自动收集并生成声明、初始化与终结代码。空字符串即其中之一_Py_STR(empty)。动态驻留针对运行期新产生的字符串如编译时出现的标识符、属性名等按需把字符串加入驻留集合以换取指针比较的加速。对解释器而言驻留最直接的收益是对频繁重复出现的名字属性名、关键字等LOAD_ATTR/字典查找等热点操作可退化为常量时间且无哈希的指针比较。列表排序单独保留的算法文献Listsort 的详解文档刻意保留在InternalDocs目录之外的原始位置即 Objects/listsort.txtREADME 中以注释说明这一刻意安排与list.sort()的实现 Objects/listobject.c 配套阅读。其中描述了 CPython 自适应归并排序的细节包括 run 检测、minrun、galloping 策略与归并平衡等是研究 CPython 排序性能的权威一手材料。模块级内部实现以 asyncio 为例asyncio.md 展示了模块内部文档的典型写法它描述 Lib/asyncio/ 及 C 加速器 Modules/_asynciomodule.c 的实现细节且明确标注以下内容描述 C 实现的实现细节。以任务管理为例文档对比了 3.14 前后的两代实现Python 3.14 之前C 实现用WeakSet存放事件循环创建的全部任务避免事件循环强引用任务、允许任务不再需要时被 GC 回收并用{EventLoop: Task}字典记录每个事件循环的当前任务/* Dictionary containing tasks that are currently active in all running event loops. {EventLoop: Task} */ PyObject *current_tasks; /* WeakSet containing all tasks scheduled to run on event loops. */ PyObject *scheduled_tasks;文档指出该实现的两个缺陷一是维护大量弱引用及其清理回调增大了 GC 负担在任务数量庞大的应用中成为瓶颈内存占用更高、性能更低二是查当前任务需要做一次字典查找较慢。3.14 的重设计即针对这些问题展开。同一文档还继续深入任务调度、回调与未来对象等在 C 层的实现方式是理解 asyncio 性能特性的窗口。如何高效利用这套文档综合以上各篇给希望深入 CPython 内部读者几条实用路径按需导航而非从头通读InternalDocs/README.md本身就是一个主题 → 文档 → 源码的索引。遇到具体问题异常怎么处理生成器怎么恢复先读对应文档再由文内链接跳进 Grammar/python.gram、Parser/parser.c、Python/compile.c、Python/assemble.c、Python/ceval.c、Objects/genobject.c 等源码。把再生成当作实验循环改语法、AST 或全局单例后按 changing_grammar.md 的清单执行make regen-token/make regen-pegen/make regen-ast/make regen-global-objects异常时先make clean。对照测试验证理解源码配套的Lib/test/如test_grammar.py、test_builtin.py是验证你对行为理解是否正确的最直接手段。留意版本差异文档所述如_PyStackRef、QSBR、JIT、按实际栈深度的防护、内嵌字节码的co_code_adaptive未必适用于旧版 CPython 或其他 Python 实现本文与这些文档所描述的均以当前仓库的代码状态为准。这套目录 专题文档 源码定位的编排方式让InternalDocs/成为连接 CPython 外部文档Doc/与底层实现Grammar、Include、Parser、Python、Objects、Modules之间的桥梁——无论你是想为 CPython 贡献代码、调试扩展模块还是纯粹想理解Python 解释器内部究竟发生了什么它都是最可靠的起点。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考