CPython 3.19 移除预告:ctypes、hashlib、http.cookies、imaplib、tkinter 五大弃用项的源码级解析
本篇基于 CPython 仓库中 Doc/deprecations/pending-removal-in-3.19.rst 这份官方弃用清单展开,逐项对照当前源码仓库(版本 3.16.0a0,见 Include/patchlevel.h)中各弃用警告的实际实现位置,说明每一项在 3.19 将被移除的 API 是什么、现在会收到什么警告、以及如何改写代码才能平滑完成迁移。读完后,你可以直接在自己的项目里按清单排查代码,并用本文给出的源码证据确认每一处行为变更。
弃用总览:3.19 将移除的 API 清单
官方文档将该清单按模块组织,共涉及 5 个标准库模块:
| 模块 | 待移除项 | 移除版本 |
|---|---|---|
ctypes |
在非 Windows 平台上仅设置 _pack_ 而不设置 _layout_,从而隐式切换到 MSVC 兼容结构体布局 |
3.19(该隐式默认行为将变为错误) |
hashlib |
哈希构造函数的 string= 关键字参数(如 hashlib.new()、hashlib.md5()、hashlib.sha256()) |
3.19 |
http.cookies |
http.cookies.Morsel.js_output() 方法 |
3.19 |
http.cookies |
http.cookies.BaseCookie.js_output() 方法 |
3.19 |
imaplib |
对 IMAP4.file 属性赋值(改变其值) |
3.19 |
tkinter |
tkinter.filedialog.askopenfiles() 函数(自 3.16 起弃用) |
3.19 |
需要强调的迁移总原则:这些 API 在 3.15/3.16 时代已被标记弃用(发出 DeprecationWarning),当前 3.16 开发周期中它们仍然可用但会报警告;到 3.19 时,相关代码路径将被删除或改为抛出错误。如果你的项目需要同时兼容旧版本与 3.19,应尽早消除对这些警告的依赖。
ctypes:_pack_ 不再隐式决定 MSVC 布局
文档所述行为
清单原文:在非 Windows 平台上,仅通过设置 Structure._pack_(而不设置 Structure._layout_)来隐式切换到 MSVC 兼容的结构体布局,这一行为将不再支持。
背景是:ctypes 的 Structure/Union 类在计算内存布局时,Windows(MSVC)与 GCC/SysV ABI 对成员对齐、尤其是指针与整型混排场景的处理并不完全一致。_pack_ 传统上用于控制压缩对齐,而 CPython 后来引入了显式的 _layout_ 类属性来二选一:
_layout_ = 'ms':按 MSVC(Windows)规则计算布局;_layout_ = 'gcc-sysv':按 GCC/SysV 规则计算布局。
源码中的警告实现
从源码结构看,这段逻辑位于 Lib/ctypes/_layout.py 的布局计算入口处,核心分支可以归纳为:
pack = getattr(cls, '_pack_', None)
layout = getattr(cls, '_layout_', None)
if layout is None:
if sys.platform == 'win32':
gcc_layout = False
elif pack:
# 非 Windows + 只设了 _pack_:隐式使用 MSVC 布局
warnings._deprecated(
'_pack_ without _layout_',
f"Due to '_pack_', the '{cls.__name__}' {base_type_name} will "
+ "use memory layout compatible with MSVC (Windows). "
+ "If this is intended, set _layout_ to 'ms'. "
+ "The implicit default is deprecated and slated to become "
+ "an error in Python {remove}.",
remove=(3, 19),
)
gcc_layout = False
else:
gcc_layout = True
elif layout == 'ms':
gcc_layout = False
elif layout == 'gcc-sysv':
gcc_layout = True
else:
raise ValueError(f'unknown _layout_: {layout!r}')
注意源码里给出的关键限定:警告只在非 Windows 平台且未显式设置 _layout_ 时触发;在 Windows 上 _pack_ 隐式对应 MSVC 布局本来就是默认行为,不受影响。若传入未知值(既不是 'ms' 也不是 'gcc-sysv'),当前版本会直接抛 ValueError。
迁移方式
如果你的跨平台代码确实依赖 MSVC 布局(例如与 Windows 端共享的二进制协议),把隐式写法改为显式:
import ctypes
class Packet(ctypes.Structure):
_pack_ = 1
_layout_ = 'ms' # 显式声明 MSVC 布局,消除 DeprecationWarning
_fields_ = [
('length', ctypes.c_uint32),
('payload', ctypes.POINTER(ctypes.c_char)),
]
如果你实际想要的是 Linux/macOS 上的 GCC/SysV 布局,则显式写 _layout_ = 'gcc-sysv'。到 3.19,"只设 _pack_ 不设 _layout_"的组合在该平台上将按警告文案所述变成错误,届时缺少 _layout_ 的跨平台结构体定义将无法再隐式工作。
同文件中还可见布局算法对两种模式的详细注释('ms' 模式在位域打包上与 -mms-bitfields 行为一致),需要精确理解对齐差异的读者可直接阅读 Lib/ctypes/_layout.py。
hashlib:string= 关键字参数将被移除
文档所述行为
清单说明:hashlib.new() 以及 hashlib.md5()、hashlib.sha256() 等按算法命名的直接构造函数,其可选的初始数据参数历史上允许用关键字 data= 或 string= 传入。string= 这个参数名已被弃用,计划于 3.19 移除。文档还特别提醒:在 3.13 之前,string 关键字参数在不同后端实现下支持不一致(OpenSSL C 后端与纯 Python 后端的签名处理曾有差异),因此官方建议始终将初始数据作为位置参数传递,以获得最大的向后兼容性。
源码中的实现
当前仓库中,按算法名生成的构造函数模板位于 Lib/hashlib.py,其中为不可用算法安装的桩函数明确展示了参数语义:
def {__func_name}(data=__UNSET, *, usedforsecurity=True, string=__UNSET):
if data is __UNSET and string is not __UNSET:
import warnings
warnings.warn(
"the 'string' keyword parameter is deprecated since "
"Python 3.15 and slated for removal in Python 3.19; "
"use the 'data' keyword parameter or pass the data "
"to hash as a positional argument instead",
DeprecationWarning, stacklevel=2)
if data is not __UNSET and string is not __UNSET:
raise TypeError("'data' and 'string' are mutually exclusive "
"and support for 'string' keyword parameter "
"is slated for removal in a future version.")
raise ValueError("unsupported hash algorithm {__func_name}")
这段代码透露了三个关键约束,迁移时都应注意:
data与string互斥:同时传两者会立即抛TypeError,不存在"两者合并"的语义;- 警告自 3.15 起发出,移除目标为 3.19,与你当前运行的 3.16 开发版一致;
- 源码注释明确写道(第 273 行附近):"The following code can be simplified in Python 3.19 once 'string' is removed from the signature"——即 3.19 中整个
string参数将从签名里消失,届时hashlib.sha256(string=b"x")将因参数不存在而抛TypeError,而不再是"带警告地工作"。
测试侧的证据在 Lib/test/test_hashlib.py,其中定义了与上面完全一致的警告文案常量,用于断言弃用行为,说明这是被测试固化下来的契约。
迁移方式
import hashlib
# 旧写法(3.19 起将抛 TypeError)
digest = hashlib.md5(string=b"payload")
# 推荐写法一:位置参数(兼容性最好,含 3.13 以前)
digest = hashlib.md5(b"payload")
# 推荐写法二:data= 关键字(3.13+)
digest = hashlib.sha256(data=b"payload")
注意:分块喂数据不受影响,h = hashlib.sha256(); h.update(chunk) 始终是正确姿势;本次弃用只针对构造时传入的初始数据关键字名。
http.cookies:js_output() 双方法移除
文档所述行为
清单将 http.cookies.Morsel.js_output() 与 http.cookies.BaseCookie.js_output() 列为将在 3.19 移除。这两个方法用于把 Cookie 对象渲染成 JavaScript 代码字符串(早期网页常用 document.cookie = ... 的方式向客户端种 Cookie),属于标准库中年代较久、在现代 Web 实践中基本被淘汰的输出路径。
源码中的实现
在 Lib/http/cookies.py 中,实现采用了"内部实现 + 弃用门面"的两层结构:
def _js_output(self, attrs=None):
"""Internal implementation without deprecation warning."""
...
def js_output(self, attrs=None):
warnings._deprecated(
"http.cookies.Morsel.js_output",
...
# remove 目标指向 3.19
)
return self._js_output(attrs)
BaseCookie.js_output 在 Lib/http/cookies.py 处同样是先调用 warnings._deprecated("http.cookies.BaseCookie.js_output", ...),再逐成员委托给 value._js_output(attrs) 聚合输出。这一结构说明:警告只挂在公开的 js_output 入口上,内部的 _js_output 仍会保留以服务该门面,直到 3.19 一并移除。
测试方面,Lib/test/test_http_cookies.py 中的用例用 assertWarnsRegex(DeprecationWarning, r"BaseCookie\.js_output") 包裹对 js_output() 的调用,精确断言警告类型与目标名称,可直接作为"该警告在 3.16 中仍然发出"的验证依据。
迁移方式
js_output 的替代思路取决于你原来想做什么:
- 如果目的是在服务端设置 Cookie 响应头:根本不需要
js_output,直接用cookie["name"] = value后把cookie.output()放进Set-Cookie头即可; - 如果确实要在浏览器侧种 Cookie:由你自己的前端模板生成 JS 代码(如
document.cookie = "name=value; path=/"),并对值做转义,不再依赖标准库的输出格式——这也正是官方弃用它的理由:该输出格式针对的是远古浏览器环境,维护价值低。
import http.cookies
cookie = http.cookies.SimpleCookie()
cookie["session"] = "abc123"
cookie["session"]["path"] = "/"
# 服务端:直接取 Set-Cookie 头
print(cookie.output())
imaplib:IMAP4.file 属性已无用武之地
文档所述行为
清单说明:对 IMAP4.file 属性赋值已被弃用、计划 3.19 移除。该属性如今已不被使用,改变它的值也不会自动关闭当前的文件对象。文档给出的历史脉络是:在 3.14 之前,这个属性用于实现 IMAP4 对应的 read()/readline() 方法;3.14 起该机制不再被采用。
源码中的实现
Lib/imaplib.py 中的源码与文档描述逐字对应。构造函数里的注释直接解释了保留该属性的原因:
# Since IMAP4 implements its own read() and readline() buffering,
# the '_imaplib_file' attribute is unused. Nonetheless it is kept
# and exposed solely for backward compatibility purposes.
self._imaplib_file = self.sock.makefile('rb')
@property
def file(self):
import warnings
warnings._deprecated("IMAP4.file", remove=(3, 19))
return self._imaplib_file
@file.setter
def file(self, value):
import warnings
warnings._deprecated("IMAP4.file", remove=(3, 19))
# Ideally, we would want to close the previous file,
# but since we do not know how subclasses will use
# that setter, it is probably better to leave it to
# the caller.
self._imaplib_file = value
两处细节值得注意:
- 读取(getter)与写入(setter)都会触发警告,且
remove=(3, 19)明确了移除版本; - 源码注释承认理想情况下 setter 应关闭旧文件,但为避免破坏未知子类行为,选择"由调用者自行处理"——这正是文档所说"改变其值不会自动关闭当前文件"的实现原因。
至于 read()/readline() 为何不再依赖它,源码注释(Lib/imaplib.py)解释:为保证 socket 超时(IDLE 命令期间)后缓冲读取仍能工作,IMAP4 实现了自己的带缓冲读取,而标准库 SocketIO 的超时处理无法满足该需求,因此旧的文件对象机制整体被绕过。
迁移方式
检查代码中所有 connection.file = ... 的赋值并删除即可;若子类曾经通过替换该文件对象来介入读取流程,应改为重写 read()/readline() 或在上层包装 socket 通信。测试目录中的 Lib/test/test_imaplib.py 覆盖了 IMAP4 的读写行为,可作为改写后的回归验证参照。
tkinter:askopenfiles() 的替代写法
文档所述行为
清单最后一条:tkinter.filedialog.askopenfiles() 自 Python 3.16 起弃用(由 Serhiy Storchaka 贡献,对应上游 issue 152638)。官方给出的替代方案很明确:遍历 askopenfilenames() 返回的文件名,逐个自行打开。注意这条与其余条目略有不同——它不是 3.15/3.16 新弃用,而是把 3.16 已发出的弃用正式排入 3.19 移除时间表。
源码中的实现
Lib/tkinter/filedialog.py 中的实现本身就是一份可读的迁移样例:
def askopenfiles(mode = "r", **options):
"""Ask for multiple filenames and return the open file
objects
returns a list of open file objects or an empty list if
cancel selected
"""
import warnings
warnings._deprecated(
"tkinter.filedialog.askopenfiles",
message=f"{warnings._DEPRECATED_MSG}; iterate over the names returned "
"by askopenfilenames() and open them instead",
remove=(3, 19))
files = askopenfilenames(**options)
return [open(filename, mode) for filename in files]
可见弃用后的函数体等价于"调 askopenfilenames() + 列表推导打开文件",警告文案也内嵌了官方推荐的改写方式。mode 参数只影响打开方式,对话框选项全部透传 **options,因此迁移时除 mode 外无需其他调整。
迁移方式
import tkinter.filedialog as filedialog
# 旧写法(3.19 起被移除)
files = filedialog.askopenfiles(mode="r", parent=win)
# 新写法
names = filedialog.askopenfilenames(parent=win)
files = [open(name, "r") for name in names]
try:
... # 使用 files
finally:
for f in files:
f.close()
一个实际收益:新写法把文件的生命周期交回调用者,你可以用 with 或统一的 finally 精确控制关闭时机,而旧 API 返回的已打开文件对象由谁、何时关闭并不清晰。
迁移验证:如何确认你的代码已清理干净
在升级前,建议按以下顺序做一轮排查:
- 全局搜索弃用符号:
string=传给hashlib构造函数的调用、js_output、askopenfiles、对IMAP4实例的.file赋值,以及缺少_layout_的_pack_结构体定义; - 开启默认警告运行测试:上述每一处都会发出
DeprecationWarning,其中 hashlib 的警告文案、http.cookies的警告(如BaseCookie.js_output)与 Lib/test/test_hashlib.py、Lib/test/test_http_cookies.py 中固化的正则一致,可作为自动化断言的参考; - 对照清单逐项销号:五项条目均有明确的移除版本 3.19 与替代写法,本仓库当前版本(3.16.0a0)保留全部旧行为但会报警告,正好适合作为迁移期的"灰度"环境验证改写效果。
小结
Doc/deprecations/pending-removal-in-3.19.rst 这份清单虽然只有五个条目,却覆盖了五个风格各异的标准库模块:ctypes 的 ABI 布局显式化、hashlib 的参数命名收敛、http.cookies 的过时输出路径清理、imaplib 的内部机制解耦、tkinter 的文件对话框瘦身。它们的共同规律是——先用 warnings._deprecated(..., remove=(3, 19)) 或等价机制在中间版本发出警告,再在目标版本删除,源码中的警告文案本身就写明了替代方案。掌握这一模式后,读者不仅可以完成本文列出的五项迁移,也能举一反三地处理 CPython 后续版本弃用清单中的同类条目。
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 StartedRust0623
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