CPython 3.19 C API 废弃解读:PEP 456 字符串哈希方案定制支持(Embedder Support)的移除
本篇技术解读基于 CPython 仓库中的废弃公告文件 c-api-pending-removal-in-3.19.rst。该文档宣布:在 Python 3.19 中,将移除「PEP 456 为嵌入方(embedder)提供的字符串哈希方案定义支持」。读完后你将理解 PEP 456 曾向 C 嵌入层暴露了哪些哈希定制接口、这些接口在 CPython 源码中的具体落点,以及嵌入式集成方(自定义 Python 宿主应用、C 扩展构建系统)在 3.19 到来前应如何完成迁移。
一、公告本身:一行声明背后的含义
CPython 仓库在 Doc/deprecations/ 目录下维护了一批按目标版本归类的 C API 废弃追踪文档。其中 c-api-pending-removal-in-3.19.rst 的完整内容只有一条条目:
Pending removal in Python 3.19
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* :pep:`456` embedders support for the string hashing scheme definition.
这条声明的技术含义是:PEP 456 当年除了改变解释器内部的字符串哈希实现,还额外开放了一组「让嵌入方在编译期定义字符串哈希方案」的 C 宏与 API。这一组面向 embedder 的定制能力(而非 PEP 456 的哈希随机化本身)将要在 3.19 中被删除。
二、背景:PEP 456 的 embedder 接口在 CPython 中的落点
PEP 456(随 Python 3.4 引入)把 Python 字符串的哈希从固定的 DJBX33A 变体更换为抗碰撞攻击的 SipHash 系列算法,并引入哈希随机化。与此同时,它向嵌入层开放了三个定制维度,全部集中在公开头文件 pyhash.h 中:
2.1 算法选择:Py_HASH_ALGORITHM 与四个取值
pyhash.h 中定义了可选算法枚举及默认值选择逻辑:
#define Py_HASH_EXTERNAL 0
#define Py_HASH_SIPHASH24 1
#define Py_HASH_FNV 2
#define Py_HASH_SIPHASH13 3
#ifndef Py_HASH_ALGORITHM
# ifndef HAVE_ALIGNED_REQUIRED
# define Py_HASH_ALGORITHM Py_HASH_SIPHASH13
# else
# define Py_HASH_ALGORITHM Py_HASH_FNV
# endif
#endif
- 默认情况下,支持对齐内存访问的平台使用 SipHash13,不支持的平台回退到 FNV;
- 头文件注释明确指出「The values for Py_HASH_* are hard-coded in the configure script」,即算法值由构建配置固化;
- 嵌入方此前可以通过在编译定义中强制
Py_HASH_ALGORITHM(例如设为Py_HASH_EXTERNAL)来接管字符串哈希的计算。
2.2 短字符串优化阈值:Py_HASH_CUTOFF
pyhash.h 还定义了短字符串快速哈希的截断长度:
/* Cutoff for small string DJBX33A optimization in range [1, cutoff).
*
* About 50% of the strings in a typical Python application are smaller than
* 6 to 7 chars. However DJBX33A is vulnerable to hash collision attacks.
* NEVER use DJBX33A for long strings!
*/
#ifndef Py_HASH_CUTOFF
# define Py_HASH_CUTOFF 0
#elif (Py_HASH_CUTOFF > 7 || Py_HASH_CUTOFF < 0)
# error Py_HASH_CUTOFF must in range 0...7.
#endif
取值被硬约束在 0..7 区间(0 表示禁用短字符串优化),越界会在编译期直接报错。注释同时说明了安全权衡:短字符串用快速的 DJBX33A、长字符串用 SipHash,以在典型负载(约一半字符串长度小于 6~7 字符)与抗碰撞攻击之间取得平衡。
2.3 外部哈希实现注入:PyHash_FuncDef
真正属于「embedders support for the string hashing scheme definition」的核心 API 位于 cpython/pyhash.h(该头文件仅在不定义 Py_LIMITED_API 时经 pyhash.h 间接包含):
/* hash function definition */
typedef struct {
Py_hash_t (*const hash)(const void *, Py_ssize_t);
const char *name;
const int hash_bits;
const int seed_bits;
} PyHash_FuncDef;
PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);
PyHash_FuncDef 描述了当前字符串哈希函数指针、算法名称以及哈希位宽/种子位宽;PyHash_GetFuncDef() 允许嵌入方在运行时查询这些元信息。pyhash.h 的注释还给出了外部注入方式——当 Py_HASH_ALGORITHM 为 Py_HASH_EXTERNAL 时,嵌入方可提供:
PyHash_FuncDef PyHash_Func = {...};
这一声明路径在实现层有直接对应:pyhash.c 开头即有 #if Py_HASH_ALGORITHM == Py_HASH_EXTERNAL 分支,外部模式下哈希计算委托给嵌入方提供的实现,而 SipHash13/SipHash24/FNV 各实现则分别位于该文件后续的条件编译块中。
2.4 运行时可观测性:sys.hash_info
哈希方案的当前状态还通过 C API 暴露到解释器运行时。例如 sysmodule.c 在构建 sys.hash_info 时会写入 cutoff 值(SET_HASH_INFO_ITEM(PyLong_FromLong(Py_HASH_CUTOFF)))。从源码结构看,一旦 embedder 定制接口移除,sys.hash_info 中将只反映内置算法的状态,不再存在由外部注入的算法名。
三、3.19 之后:嵌入方还能做什么
需要澄清一个关键边界:被移除的只是「方案定义/替换」能力,而非哈希随机化本身。字符串哈希仍由 CPython 内置算法计算,哈希值在不同进程间的随机性继续由 PYTHONHASHSEED 环境变量控制(该机制在官方参考文档 datamodel.rst 中有专门章节说明)。对嵌入集成方而言,迁移要点如下:
- 清理构建定义:从 C 编译参数中移除对
Py_HASH_ALGORITHM、Py_HASH_CUTOFF的自定义覆盖。这两个宏在 3.19 中不再有「由嵌入方定义方案」的语义价值; - 移除外部哈希实现:删除自实现的
PyHash_Func变量、PyHash_FuncDef结构填充代码,以及任何依赖PyHash_GetFuncDef()返回值的hash_bits/seed_bits字段做分支适配的逻辑; - 替代策略:
- 若当初选择
Py_HASH_EXTERNAL是为了跨进程/跨版本保证哈希值稳定,改用固定PYTHONHASHSEED即可获得等价的确定性; - 若当初是出于性能原因替换为更快(但可碰撞)的算法,3.19 后该权衡不再开放给嵌入方,需接受内置算法的性能特征;
- 若当初选择
- 注意稳定 ABI 面:
PyHash_FuncDef/PyHash_GetFuncDef属于非有限 API(需包含完整 CPython 头文件且未定义Py_LIMITED_API才可见),Limited API 使用方本就不应依赖,无需迁移。
四、版本边界与仓库现状
截至当前仓库快照,pyhash.h 与 cpython/pyhash.h 中的上述宏和 API 仍然完整存在,pyhash.c 也仍保留 Py_HASH_EXTERNAL 分支。这与废弃公告的「Pending removal in Python 3.19」措辞一致:这些接口在 3.19 之前的版本中仍可用(且未标注 deprecation 警告),到 3.19 才会被移除。嵌入方在针对 3.19 及以后版本编译时,应假定这些符号不存在。
五、参考路径索引
| 内容 | 路径 |
|---|---|
| 废弃公告(本文主体) | Doc/deprecations/c-api-pending-removal-in-3.19.rst |
| 算法选择与 cutoff 宏 | Include/pyhash.h |
PyHash_FuncDef / PyHash_GetFuncDef |
Include/cpython/pyhash.h |
| 哈希算法实现与 EXTERNAL 分支 | Python/pyhash.c |
sys.hash_info 构建 |
Python/sysmodule.c |
PYTHONHASHSEED 说明 |
Doc/reference/datamodel.rst |
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00