首页
/ CPython 3.19 移除预告:ctypes、hashlib、http.cookies、imaplib、tkinter 五大弃用项的源码级解析

CPython 3.19 移除预告:ctypes、hashlib、http.cookies、imaplib、tkinter 五大弃用项的源码级解析

2026-09-06 14:47:18作者:霍妲思

本篇基于 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 兼容的结构体布局,这一行为将不再支持。

背景是:ctypesStructure/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}")

这段代码透露了三个关键约束,迁移时都应注意:

  1. datastring 互斥:同时传两者会立即抛 TypeError,不存在"两者合并"的语义;
  2. 警告自 3.15 起发出,移除目标为 3.19,与你当前运行的 3.16 开发版一致;
  3. 源码注释明确写道(第 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_outputLib/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 返回的已打开文件对象由谁、何时关闭并不清晰。

迁移验证:如何确认你的代码已清理干净

在升级前,建议按以下顺序做一轮排查:

  1. 全局搜索弃用符号string= 传给 hashlib 构造函数的调用、js_outputaskopenfiles、对 IMAP4 实例的 .file 赋值,以及缺少 _layout__pack_ 结构体定义;
  2. 开启默认警告运行测试:上述每一处都会发出 DeprecationWarning,其中 hashlib 的警告文案、http.cookies 的警告(如 BaseCookie.js_output)与 Lib/test/test_hashlib.pyLib/test/test_http_cookies.py 中固化的正则一致,可作为自动化断言的参考;
  3. 对照清单逐项销号:五项条目均有明确的移除版本 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 后续版本弃用清单中的同类条目。

登录后查看全文
热门项目推荐
相关项目推荐