news 2026/9/11 11:51:25

Python 3.15 sentinel 内置类型增强:repr 参数与可写 __module__ 全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 3.15 sentinel 内置类型增强:repr 参数与可写 __module__ 全解析

Python 3.15 sentinel 内置类型增强:repr 参数与可写module全解析

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

导读

本文围绕 CPython 仓库中sentinel内置类型的最新变更展开:自 Python 3.15(PEP 661)引入sentinel类型后,本次 NEWS 更新为其新增了repr=构造参数,并将__module__属性改为可写。文章以官方文档与源码实现为主线,结合 Objects/sentinelobject.c 与 Lib/test/test_builtin.py 中的测试用例,完整讲解 sentinel 的创建、属性、比较语义、类型提示、pickle 支持与 C API 用法,帮助读者掌握这一标识缺失值/哨兵值的最佳实践。

变更概览:NEWS 条目解读

本次变更记录在 Misc/NEWS.d/next/Core_and_Builtins/2026-05-10-16-43-50.gh-issue-148829.gscS14.rst,原文如下:

sentinelobjects now support arepr=argument and their__module__attribute is writable.

即两项能力增强:

  1. sentinel()构造时支持传入repr=关键字参数,自定义对象的字符串表示;
  2. sentinel实例的__module__属性由只读改为可写,允许手动修正或重置模块归属信息。

这两项能力分别由 Objects/sentinelobject.c 中的参数解析逻辑与 Objects/sentinelobject.c 中PyMemberDef的属性读写权限定义(0表示可读写)提供底层支撑。

sentinel 类型是什么:为什么需要它

在 Python 编程中,"哨兵值"(sentinel value)用于表示"未提供""缺失""无默认值"等特殊状态。常见做法是使用None,但当None本身是合法参数值时就会产生歧义。sentinel类型正是为此而生:每个 sentinel 对象都是唯一的,只与自身相等,适合与is运算符配合使用。

从 Doc/library/functions.rst 的官方定义看,其签名与语义如下:

sentinel(name, /, *, repr=None)
  • name:必填的位置参数,必须是str,默认用作对象的表示;
  • repr:可选关键字参数,用于自定义表示;
  • sentinel类型不支持子类化;
  • 对象的浅拷贝与深拷贝都返回其自身;
  • 对象为真值(truthy),只与自身相等。

在标准库中,sentinel 已被广泛采用,例如 Lib/dataclasses.py 中的MISSING = sentinel("MISSING")KW_ONLY = sentinel("KW_ONLY"),以及 Lib/functools.py 中的_initial_missing = sentinel('_initial_missing')。这也说明本次repr__module__增强对标准库自身的可读性维护同样有实际价值。

基础用法:创建与表示

最基本的创建方式直接传入名称字符串:

>>> MISSING = sentinel("MISSING") >>> MISSING MISSING

默认情况下repr()str()都返回name本身。从源码看,这一行为由 Objects/sentinelobject.c 的sentinel_repr函数实现:若内部repr字段非空则优先返回它,否则返回name

核心增强一:repr= 参数

基本用法

>>> MISSING = sentinel("MISSING", repr="<MISSING>") >>> MISSING <MISSING> >>> str(MISSING) '<MISSING>'

设置repr=后,repr()str()都会返回自定义字符串,这在调试输出、日志中需要更明确提示时非常有用。显式传入repr=None则与不传等价,仍回退到name

参数校验

repr参数必须为strNone,否则抛出TypeError。这一校验逻辑位于 Objects/sentinelobject.c:

if (repr == Py_None) { repr = NULL; } else if (!PyUnicode_Check(repr)) { _PyArg_BadArgument("sentinel", "argument 'repr'", "str or None", repr); return NULL; }

对应的测试用例位于 Lib/test/test_builtin.py:

def test_sentinel_repr(self): with_repr = sentinel("WITH_REPR", repr="custom") without_repr = sentinel("WITHOUT_REPR", repr=None) self.assertEqual(repr(with_repr), "custom") self.assertEqual(repr(without_repr), "WITHOUT_REPR") self.assertEqual(str(with_repr), "custom") self.assertEqual(str(without_repr), "WITHOUT_REPR") with self.assertRaisesRegex(TypeError, "repr.*str or None"): sentinel("BAD_REPR", repr=42)

底层数据结构

sentinel 对象内部通过三个字段保存信息,见 Objects/sentinelobject.c:

typedef struct { PyObject_HEAD PyObject *name; PyObject *module; PyObject *repr; } sentinelobject;

对象使用 GC 分配器创建并参与垃圾回收追踪(Py_TPFLAGS_HAVE_GC),repr字段为可空引用,构造时通过Py_XNewRef持有(Objects/sentinelobject.c)。

核心增强二:可写的module属性

语义变化

此前__module____name__一样只读;本次变更后__module__变为可写,__name__仍保持只读。成员属性表定义于 Objects/sentinelobject.c:

static PyMemberDef sentinel_members[] = { {"__name__", Py_T_OBJECT_EX, offsetof(sentinelobject, name), Py_READONLY}, {"__module__", Py_T_OBJECT_EX, offsetof(sentinelobject, module), 0}, {NULL} };

其中Py_READONLY表示只读,0表示可读写。

行为示例

>>> missing = sentinel("MISSING") >>> missing.__module__ '__main__' >>> missing.__module__ = "changed" >>> missing.__module__ 'changed' >>> del missing.__module__ # 允许删除 >>> missing.__module__ # 删除后再访问抛出 AttributeError

测试用例见 Lib/test/test_builtin.py,其中还验证了__name__赋值会抛出AttributeError,而__module__可赋值、可删除。

为何需要可写module

__module__参与 pickle 的定位逻辑:只有位于可导入模块全局作用域、且名称匹配的 sentinel 才能被序列化。若对象在模块间移动或需要在序列化时指定归属模块,可写属性就提供了修正手段(详见下文 pickle 一节)。

默认模块推断机制

创建 sentinel 时若不显式指定模块,解释器会自动推断调用方所在模块,这一逻辑由 Objects/sentinelobject.c 的caller()函数完成:它读取当前线程状态中的活动帧,获取对应函数对象并调用PyFunction_GetModule得到模块名;若当前不在函数帧内或函数无模块归属,则返回None

因此,在模块顶层创建时:

# 假设此代码位于 mypackage/constants.py MISSING = sentinel("MISSING") assert MISSING.__module__ == "mypackage.constants"

比较语义与唯一性

sentinel 对象为真值(truthy),且只与自身相等(==比较基于身份)。官方文档明确指出其设计意图是与is运算符配合使用

>>> missing = sentinel("MISSING") >>> other = sentinel("MISSING") >>> missing is other False >>> missing == missing True >>> missing == other False >>> bool(missing) True

即使两个 sentinel 使用相同的name,它们仍是不同对象——这是区别于普通字符串常量的关键。相关断言见 Lib/test/test_builtin.py。

不可子类化与类型标志

sentinel是一个不可变的最终类型:不可作为基类被继承,类型对象本身也不允许设置属性。类型定义于 Objects/sentinelobject.c:

PyTypeObject PySentinel_Type = { ... .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC, .tp_richcompare = _Py_BaseObject_RichCompare, ... };

测试用例验证了sentinel.__flags__中包含IMMUTABLETYPEHAVE_GC标志,且不含BASETYPE(Lib/test/test_builtin.py):

with self.assertRaises(TypeError): class SubSentinel(sentinel): pass

拷贝与序列化行为

拷贝返回自身

__copy____deepcopy__均直接返回对象自身(Objects/sentinelobject.c),因此:

import copy missing = sentinel("MISSING") assert copy.copy(missing) is missing assert copy.deepcopy(missing) is missing

pickle 支持(模块级与类级)

根据 Doc/library/functions.rst 的说明,pickle 支持是有条件的:

  • 位于模块全局作用域、且变量名与name匹配的 sentinel 可被序列化;
  • 位于类作用域、名称与 sentinel 的限定名(qualified name)匹配的也可序列化;
  • 定义在函数作用域内的 sentinel 不可序列化。

序列化后身份保持不变

import pickle PICKLABLE = sentinel("PICKLABLE") assert pickle.loads(pickle.dumps(PICKLABLE)) is PICKLABLE class Cls: PICKLABLE = sentinel("Cls.PICKLABLE") assert pickle.loads(pickle.dumps(Cls.PICKLABLE)) is Cls.PICKLABLE

这一行为由 Objects/sentinelobject.c 的__reduce__实现支撑:它返回name字符串,pickle 将name视为模块__module__中的全局变量名进行查找还原。

对应测试见 Lib/test/test_builtin.py:模块级与类级 sentinel 在所有 pickle 协议下均保持身份;而局部创建的 sentinel 序列化时会抛出pickle.PicklingError

类型提示中的用法

sentinel 支持|(按位或)运算符,可用于类型表达式,实现"默认值可能是 int 或缺失状态"这样的联合类型标注:

MISSING = sentinel("MISSING") def next_value(default: int | MISSING = MISSING): ...

官方文档与测试(Lib/test/test_builtin.py)均验证了以下行为:

  • missing | intint | missing均生成合法的typing.Union
  • missing | missing结果仍为missing自身;
  • missing | None生成(missing, NoneType)联合;
  • 与任意类型操作数(如list[int]int | str)组合均正常;
  • 与非法操作数(如1)组合抛出TypeError

该能力由 Objects/sentinelobject.c 中的数字协议钩子实现:

static PyNumberMethods sentinel_as_number = { .nb_or = _Py_union_type_or, };

C API 层面的支持

针对嵌入与扩展场景,Doc/c-api/sentinel.rst 提供了三个接口(均于 Python 3.15 新增):

C 接口说明
PySentinel_Typesentinel类型对象,与 Python 层的sentinel是同一对象
PySentinel_Check(o)判断o是否为 sentinel 对象或其子类;由于当前不支持子类化,该检查即精确匹配
PySentinel_CheckExact(o)判断o是否为 sentinel 对象(非子类);当前与PySentinel_Check等价
PySentinel_New(name, module_name, repr)创建新 sentinel,三个参数均为const char *name不可为NULLmodule_namerepr可为NULL,失败时返回NULL并设置异常

C 层创建函数 PySentinel_New 会逐个将 C 字符串转换为PyUnicode对象并构造实例;module_nameNULL__module__被设为NonereprNULLrepr()回退到name

关于 pickle 的 C 层注意事项:module_name必须是可导入模块名,且 sentinel 需在该模块中以与name匹配的路径可访问,否则无法完成序列化还原。

源码实现要点速览

  • 类型定义:PySentinel_Type(Objects/sentinelobject.c),同时启用IMMUTABLETYPEHAVE_GC
  • 构造入口:sentinel.__new__(clinic 生成,Objects/sentinelobject.c),name必须是strrepr必须是strNone
  • 模块推断:caller()(Objects/sentinelobject.c),基于当前帧的函数对象推导模块名;
  • 表示逻辑:sentinel_repr(Objects/sentinelobject.c),repr字段优先、否则回退name
  • 比较逻辑:复用_Py_BaseObject_RichCompare,保证基于身份的比较语义;
  • GC 支持:sentinel_traverse/sentinel_clear(Objects/sentinelobject.c)正确管理namemodulerepr三个字段的引用。

兼容性与总结

本次变更聚焦两点:sentinel(name, *, repr=...)允许自定义表示;__module__变为可写。二者均为向后兼容的增量增强——原有调用方式(仅传name)不受影响,__name__依旧只读。

结合官方文档(Doc/library/functions.rst、Doc/whatsnew/3.15.rst)、C API 文档(Doc/c-api/sentinel.rst)与完整测试(Lib/test/test_builtin.py),可以确认 sentinel 已成为 Python 3.15 处理"缺失值"场景的推荐工具:唯一身份、is比较、真值语义、类型表达式参与、可 pickle,配合新增的repr=与可写__module__,无论用于标准库常量还是自定义库 API,都能写出语义清晰、调试友好的哨兵值。

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 11:48:22

STM32F103 AB分区OTA从零实现:低成本高可靠空中升级方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 11:47:51

栈的实现与选型:数组栈与链表栈的原理、复杂度及工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 11:46:12

SAP传输请求管理:核心类型与跨系统传输实践

1. SAP系统间传输请求概述 在SAP系统环境中&#xff0c;传输请求&#xff08;Transport Request&#xff09;是系统变更管理的基础单元。作为SAP项目实施和运维的核心机制&#xff0c;它记录了从开发系统到测试系统再到生产系统的所有配置变更、程序开发和数据调整。我经历过多…

作者头像 李华
网站建设 2026/9/11 11:45:37

2026年iOS开发选型与工具链全解析:从原生到跨平台,绕开上架坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 11:44:42

G-Helper 完全教程:如何给华硕笔记本换上轻量级性能控制中心

G-Helper 完全教程:如何给华硕笔记本换上轻量级性能控制中心 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook, Expertb…

作者头像 李华