CPython C API 中 Py_None 单例对象的设计与使用:从 Py_IsNone 到 Py_RETURN_NONE 的源码级解析
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
本文围绕 CPython 官方文档 Doc/c-api/none.rst 中定义的None对象 C API 展开:Py_None宏、Py_RETURN_NONE返回宏,以及为什么 C API 不提供Py_None对应的PyTypeObject和PyNone_Check检查函数。读完本文,你将掌握在 C 扩展中正确判断和返回None的惯用写法,并理解 3.12 起Py_None成为"永生"(immortal)对象后引用计数策略的变化及其在头文件中的具体实现依据。
为什么 C API 不暴露 None 的类型对象,也没有 PyNone_Check
Doc/c-api/none.rst 开篇就给出了一个重要的 API 设计说明:
None的PyTypeObject不直接暴露在 Python/C API 中;- 由于
None是单例(singleton),在 C 中直接比较对象指针是否相等(==,即 C 中的恒等性测试)就足够了; - 出于同样的原因,C API没有提供
PyNone_Check函数。
这一点可以从源码得到印证。None的类型对象在 CPython 内部命名为_PyNone_Type,定义于 Objects/object.c,它是一个内部符号,并未通过Include下的公开头文件导出给 C 扩展作者;而单例本体_Py_NoneStruct在 Objects/object.c 中初始化:
PyObject _Py_NoneStruct = _PyObject_HEAD_INIT(&_PyNone_Type);也就是说,C 层判断"某对象是不是None"的标准做法不是查类型,而是查指针:
if (obj == Py_None) { /* obj 就是 None */ }这正是 Python 层x is None语义在 C 层的直接对应。
Py_None:指向 None 单例的宏
Doc/c-api/none.rst 对Py_None的定义是:
The Python
Noneobject, denoting lack of value. This object has no methods and isimmortal.(Python 的None对象,表示"没有值"。该对象没有任何方法,并且是永生的。)
同时文档标注了版本变更:3.12 起,Py_None是 immortal 对象。
在头文件 Include/object.h 中,其实际展开逻辑为:
/* _Py_NoneStruct is an object of undefined type which can be used in contexts where NULL (nil) is not suitable (since NULL often means 'error'). */ PyAPI_DATA(PyObject) _Py_NoneStruct; /* Don't use this directly */ #if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030D0000 # define Py_None Py_GetConstantBorrowed(Py_CONSTANT_NONE) #else # define Py_None (&_Py_NoneStruct) #endif这里有几个值得注意的实现细节:
NULL与None的区分。头文件注释明确指出,_Py_NoneStruct的存在是为了那些"不能用 NULL(NULL 通常表示错误)"的场合。C 扩展返回Py_None表示"Python 层的 None",而返回NULL表示发生了 C 层错误,两者语义完全不同,不能混用。- 稳定 ABI(Py_LIMITED_API)下的实现差异。当以 3.13 及以上的稳定 ABI 编译时,
Py_None展开为Py_GetConstantBorrowed(Py_CONSTANT_NONE)——一个借用引用(borrowed reference)的函数调用,不再直接暴露结构体地址;Py_CONSTANT_NONE这个常量 ID 在 Objects/object.c 的常量表中对应_Py_NoneStruct。而非受限 API 或 3.12 及以下稳定 ABI 下,它直接是取地址&_Py_NoneStruct。 - "no methods"。
None对象在 Python 层确实只有__class__、__doc__、__repr__等类型级行为,实例层面不提供任何可调用方法,这与文档描述一致。
用 Py_IsNone 做恒等性判断
除了裸指针比较,CPython 还提供了专门的辅助接口。在 Include/object.h 中:
// Test if an object is the None singleton, the same as "x is None" in Python. PyAPI_FUNC(int) Py_IsNone(PyObject *x); #define Py_IsNone(x) Py_Is((x), Py_None)其语义与文档中"用==测试对象恒等性"的说法完全一致,注释也直接点明它等价于 Python 的x is None。从源码结构看,Py_IsNone被导出为一个真实函数(见 Objects/object.c),以保证在abi3t等不透明对象头的构建下仍然可用。实际编写 C 扩展时,Py_IsNone(obj)是比手写obj == Py_None更符合 API 惯例的写法。
Py_RETURN_NONE 宏:immortal 状态下的引用计数差异
Doc/c-api/none.rst 还定义了返回宏:
Py_RETURN_NONE— ReturnPy_Nonefrom a function.(从函数中返回Py_None。)
这个宏看似简单,但它的实现精确反映了 3.12 的 immortal 版本变更。在 Include/object.h 中:
/* Macro for returning Py_None from a function. * Only treat Py_None as immortal in the limited C API 3.12 and newer. */ #if defined(Py_LIMITED_API) && Py_LIMITED_API+0 < 0x030c0000 # define Py_RETURN_NONE return Py_NewRef(Py_None) #else # define Py_RETURN_NONE return Py_None #endif两种展开形式的区别在于引用计数:
- 老路径(Py_LIMITED_API < 3.12):
Py_None还是普通对象,函数返回的是"新引用",必须用Py_NewRef(Py_None)递增一次引用计数,否则调用方持有引用后对象会少计一次。 - 新路径(默认,或稳定 ABI 3.12+):
Py_None是 immortal 对象,Py_INCREF对永生对象是无操作,直接return Py_None即可。
Immortal 对象在 CPython 中的实现策略
文档中"immortal"一词值得展开。CPython 对永生对象有一套完整的引用计数策略,说明位于 Include/refcount.h,其核心思想是:
- 在 64 位系统上,引用计数低 32 位达到
2**31及以上的对象被视为 immortal(_Py_IMMORTAL_MINIMUM_REFCNT),初始值设为3 << 30(_Py_IMMORTAL_INITIAL_REFCNT); - 这样设计保证了向后兼容:用旧版
Py_INCREF/Py_DECREF的 C 扩展(如针对 3.11 及更早编译的 abi3 模块)继续增减引用计数时,即使计数偏移约 10 亿次,也不会跌破永生阈值,执行仍然正确; - 在 32 位系统上,阈值降低为
2**30; - 引用计数递增使用饱和算术(saturated arithmetic),保证永生对象的计数不会溢出。
正因为Py_None作为静态分配的永生单例永远不会被释放,3.12 起的Py_RETURN_NONE才能省去Py_NewRef这一步——文档中的versionchanged:: 3.12标注与头文件中的条件编译分支是相互印证的。
实践要点小结
综合 Doc/c-api/none.rst 的规范与上述源码证据,C 扩展中与None打交道的正确姿势可以归纳为:
- 获取:直接使用
Py_None宏,不要手写&_Py_NoneStruct(头文件注释明确标注了 "Don't use this directly"); - 判断:优先用
Py_IsNone(obj),其语义等价于 Python 的x is None;不需要也不应该寻找PyNone_Check; - 返回:使用
Py_RETURN_NONE宏,让头文件按当前 API 版本自动选择是否需要Py_NewRef; - 不要暴露类型:
_PyNone_Type是内部实现,C API 层面判断None恒等即可,这是官方文档明确的设计决策而非遗漏。
以上写法以当前 CPython 仓库的头文件与实现为准;如果你的扩展面向 3.11 及更早版本(Py_LIMITED_API < 0x030c0000),Py_None仍按普通引用计数对象处理,Py_RETURN_NONE会自动补上Py_NewRef,无需手写。
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考