CPython C API 具体对象层详解:如何安全地创建、检查与操作 Python 内置对象
本篇围绕 CPython 官方文档的 Concrete Objects Layer(具体对象层)章节展开,系统梳理该层 C API 的"对象族谱"式组织结构、逐类调用前的类型检查纪律与 NULL 传入风险,并结合 Objects/ 目录下 PyType_Type、PyLong_FromLong 等源码实现,说明小整数缓存、immortal 单例等底层机制,帮助你编写既正确又高效的 C 扩展模块与嵌入层代码。
1. 具体对象层(Concrete Objects Layer)的定位
concrete.rst 描述的"具体对象层"是 Python C API 中针对特定 Python 对象类型的那一组函数。它与抽象层(PySequence_*、PyMapping_* 等基于协议的通用接口)形成对比:抽象层可以操作任何实现了对应协议的对象,而具体层函数只对某一确切类型有效。
文档开篇给出了两条必须牢记的使用纪律:
- 先做类型检查,再调用具体层函数。向具体层函数传入错误类型的对象"不是一个好主意"。如果你从 Python 程序接收到一个对象且不确定其类型,必须先执行类型检查——文档给出的例子是:要确认对象是字典,先用
PyDict_Check。 NULL不会被拦截。原文的警告框明确指出:本章描述的函数会仔细检查传入对象的类型,但其中许多并不检查传入的是不是NULL而非有效对象;允许传入NULL可能导致内存访问违规并直接终止解释器。因此从 Python 侧拿到的每个对象都应在使用前先判空。
整个章节按 Python 对象类型的"族谱"(family tree)组织,从上至下分为五大部分,每一部分对应一组子章节:
| 部分 | 子章节(对应文档) |
|---|---|
| Fundamental Objects(基础对象) | Type Objects、The None Object |
| Numeric Objects(数值对象) | Integer、Boolean、Float、Complex |
| Sequence Objects(序列对象) | Bytes、Byte Array、Unicode、Tuple、List |
| Container Objects(容器对象) | Dictionary、Set |
| Function Objects(函数对象) | Function、Method、Cell、Code |
| Other Objects(其他对象) | File、Module、Iterator、Descriptor、Slice、MemoryView、PickleBuffer、WeakRef、Capsule、Sentinel、Frame、Generator、Coro、ContextVar、Type Hints |
| C API for extension modules(类型专属模块 API) | Curses、Datetime |
2. 基础对象:Type Objects 与 None
2.1 Type Objects:元编程的 C 侧入口
Type Objects 章节描述用于描述内置类型的 C 结构及其操作函数,核心要素包括:
PyTypeObject:所有内置类型对象使用的 C 结构;PyType_Type:类型对象的类型对象,与 Python 层的type是同一个对象。在源码中,它定义于 Objects/typeobject.c,以静态初始化器PyVarObject_HEAD_INIT(&PyType_Type, 0)完成自举——type是自身类型的实例,这一"自引用"结构是整个类型系统的基石。
常用检查与查询函数:
PyType_Check(PyObject *o):o是类型对象(包括type的派生类的实例)时返回非零,总是成功;PyType_CheckExact(PyObject *o):o是类型对象但不是标准类型对象的子类型时返回非零;PyType_GetFlags(PyTypeObject *type):返回tp_flags成员。该函数主要为Py_LIMITED_API场景设计——各个 flag 位跨 Python 版本保证稳定,但直接访问tp_flags本身不属于受限 API;PyType_GetDict(PyTypeObject *type)(3.12 起):返回类型对象内部命名空间(正常情况下仅通过只读代理cls.__dict__暴露),返回值必须按只读对待;它是直接访问tp_dict的替代方案,面向特定的嵌入与语言绑定场景。
类型缓存相关的两个函数值得扩展模块开发者注意:
PyType_Modified(PyTypeObject *type):在手动修改类型的属性或基类之后必须调用,以失效该类型及其所有子类型的内部查找缓存;PyType_ClearCache():清除内部查找缓存并返回当前版本标签。文档注明(versionchanged 3.16):由于类型缓存现已按类型(per-type)实现,该函数在新版本中已变为 no-op,但仍返回当前版本标签。
此外,3.12 引入了类型观察者(watcher)机制,用于跨解释器/动态类型场景感知类型变更:
PyType_AddWatcher(PyType_WatchCallback callback):注册观察者回调,返回非负整数 ID;出错返回-1并设置异常。文档特别警告:在 free-threaded 构建中该函数非线程安全,必须在启动时(派生第一个线程之前)调用;PyType_ClearWatcher(int watcher_id):清除指定 ID 的观察者,成功返回 0,失败返回 -1;扩展代码绝不应传入非PyType_AddWatcher返回的 ID;PyType_Watch(int watcher_id, PyObject *type):将某个类型标记为被观察,之后PyType_Modified报告变更时回调即被触发;被观察的堆类型被释放时也会触发回调。
2.2 None 对象:用身份比较代替类型检查
None 对象章节非常短但信息密度高:
None的PyTypeObject没有在 Python/C API 中直接暴露;- 因为
None是单例,用 C 的==做对象身份比较就足够了,所以也不存在PyNone_Check这类函数; Py_None(PyObject *):表示"无值"的 PythonNone对象,没有任何方法,并且自 3.12 起是 immortal(永生对象)——它不参与引用计数管理,扩展模块既不需要也不应该手动增减它的引用计数;- 宏
Py_RETURN_NONE:从 C 函数返回Py_None的便捷写法。
3. 数值对象(Numeric Objects)
3.1 Integer Objects:任意精度整数与小整数缓存
Integer Objects 章节说明:Python 中所有整数都实现为任意精度的 "long" 整数对象。核心 API 与要点:
PyLongObject:表示 Python 整数对象的PyObject子类型;PyLong_Type:与 Python 层int相同的类型对象;PyLong_Check/PyLong_CheckExact:类型检查,前者接受子类型,总是成功;- 错误约定:大多数
PyLong_As*API 出错时返回(return type)-1,这与真实数值 -1 无法区分,必须用PyErr_Occurred消歧。
PyLong_FromLong(long v) 从 C long 创建新的 PyLongObject,失败返回 NULL。文档中的实现细节(impl-detail)与源码可以相互印证:CPython 为 -5 到 1024 之间的所有整数保留了一个对象数组,创建该范围内的 int 时实际返回的是已有对象的引用。在源码 Objects/longobject.c 中可以看到这一机制:PyLong_FromLong 展开为 PYLONG_FROM_INT 宏,而 PYLONG_FROM_UINT 宏对"小整数"分支直接调用 get_small_int((sdigit)(ival)) 返回缓存对象,仅当值超出小整数范围时才走 long_alloc(ndigits) 分配新对象。这意味着 C 扩展中反复创建 0~1024 的 int 是零分配操作,也是 Python 层面 a is b 对小整数成立的原因。
除 FromLong 外,该章节还提供 PyLong_FromUnsignedLong、PyLong_FromSsize_t、PyLong_FromSize_t、PyLong_FromLongLong、PyLong_FromUnsignedLongLong 等覆盖各种 C 整数宽度的构造函数(3.13 起另有 PyLong_FromInt32 等),以及对应的 PyLong_As* 提取函数族。
3.2 其余数值类型
- Boolean Objects:
PyBoolObject与PyBool_Type;注意 bool 是 int 的子类,bool对象只有Py_True/Py_False两个单例; - Float Objects:
PyFloatObject、PyFloat_FromDouble、PyFloat_AsDouble等; - Complex Objects:
PyComplexObject、PyComplex_FromCComplex等。
4. 序列对象(Sequence Objects)
concrete.rst 在该部分开篇说明:序列对象的通用操作(长度、索引、切片等基于 PySequence_* 协议)已在前一章(sequence.rst)讨论;本节处理 Python 语言内置的序列具体类型。文档列出的五类及其文档为:
- Bytes Objects:不可变字节串;
- Byte Array Objects:可变字节数组;
- Unicode Objects:Python 的 str 文本对象;
- Tuple Objects:不可变序列;
- List Objects:动态数组。
从源码结构看,每一类都遵循相同的"文档—头文件—实现"三件套布局,便于按类型定位实现:
| 类型 | 文档 | 公开头文件 | 实现文件 |
|---|---|---|---|
| bytes | bytes.rst | bytesobject.h | bytesobject.c |
| bytearray | bytearray.rst | bytearrayobject.h | bytearrayobject.c |
| str | unicode.rst | unicodeobject.h | unicodeobject.c |
| tuple | tuple.rst | tupleobject.h | tupleobject.c |
| list | list.rst | listobject.h | listobject.c |
5. 容器对象(Container Objects)
Dictionary objects 章节给出的核心 API 是 concrete.rst 开头示例的来源:
PyDictObject:PyObject子类型,表示 Python 字典;PyDict_Type:与 Python 层dict相同的类型对象;PyDict_Check(PyObject *p):p是 dict 或 dict 子类型的实例时为真,总是成功——这正是主文档中"不确定类型时先检查"的标准示范;PyDict_CheckExact(PyObject *p):仅当p是 dict 本身(不含子类型实例)时为真;PyDict_New():返回新的空字典,失败返回NULL。
Set Objects 章节则覆盖 PySetObject/PyFrozenSetObject、PySet_New 等集合操作 API。实现上分别位于 dictobject.c 与 setobject.c。
6. 函数对象(Function Objects)
Function Objects 部分收录了"可调用物"及其支撑结构对应的四篇子文档:
- Function Objects:
PyFunctionObject与PyFunction_New等,实现见 funcobject.c; - Method Objects:
PyMethodObject,函数绑定到对象/类上的形态,实现见 methodobject.c; - Cell Objects:闭包中捕获自由变量的载体
PyCellObject,实现见 cellobject.c; - Code Objects:编译后的字节码对象
PyCodeObject,实现见 codeobject.c。
7. 其他对象(Other Objects)
Other Objects 部分是族谱中"杂项但高频"的一层,concrete.rst 依次列出 15 个子章节,每一篇都遵循"类型结构 → 类型对象 → Check 函数 → 构造/操作函数"的相同编排:
- File Objects:
PyFileObject、PyFile_FromFd、PyFile_NameFromObject等(实现 fileobject.c); - Module Objects:
PyModuleObject、PyModule_New等(实现 moduleobject.c); - Iterator Objects:
PySeqIter_New、PyIter_Next等(实现 iterobject.c); - Descriptor Objects:
PyGetSetDef、成员描述符相关 API(实现 descrobject.c); - Slice Objects:
PySliceObject与PySlice_GetIndices*(实现 sliceobject.c); - MemoryView Objects:
PyMemoryViewObject、PyMemoryView_FromMemory等(实现 memoryobject.c); - PickleBuffer Objects:pickle 导出缓冲协议的对象(实现 picklebufobject.c);
- WeakRef Objects:
PyWeakref_NewRef等(实现 weakrefobject.c); - Capsule Objects:
PyCapsule是跨模块传递不透明 C 指针/对象的标准容器(实现 capsule.c); - Sentinel Objects:
PySentinelObject,如用于sys.intern等特殊用途的单例; - Frame Objects:
PyFrameObject的检视与操作(实现 frameobject.c); - Generator Objects:
PyGenObject的创建与驱动(实现 genobject.c); - Coro Objects:
PyCoroObject的 C 侧 API; - ContextVar Objects:
PyContextVar类型与pycontextvars机制(C 实现位于 Python/_contextvars.c); - Type Hints Objects:
TypeVar与泛型别名相关对象(实现 typevarobject.c 与 genericaliasobject.c)。
8. 类型专属模块 API:curses 与 datetime
concrete.rst 的最后一部分"C API for extension modules"收录了两个绑定到特定 Python 类型的模块级 C API:
- Curses Windows:
_curseswindow类型对象的操作 API(C 实现 Modules/_cursesmodule.c); - Datetime Objects:
PyDateTime系列 API,操作datetime.date、datetime.time等具体类型(C 实现 Modules/_datetimemodule.c,公开头文件 datetime.h)。
这两篇与其他部分的区别在于:它们不是解释器核心内置类型的 API,而是标准库模块对外暴露的、与其自定义类型绑定的 C 接口。
9. 工程实践要点:把文档纪律落到代码里
综合 concrete.rst 的警告与各子文档的函数契约,C 扩展中使用具体对象层 API 的安全模式可以归纳为:
- 来源不可信则必检查:从 Python 侧取得的对象,先
PyXxx_Check(接受子类型)或PyXxx_CheckExact(只要精确类型),再调用具体层函数; NULL自负:具体层函数不替你挡NULL,调用链上游保证非空,否则可能直接触发内存访问违规并终止解释器;- 区分"总是成功"与"可失败"的函数:各
*_Check系列文档均注明"This function always succeeds",而PyXxx_From*构造函数以NULL表示失败、PyXxx_As*提取函数以-1(或-1/-1.0等类型化错误值)表示失败且需配合PyErr_Occurred消歧(long.rst 明确指出了该歧义); - 单例对象特殊对待:
Py_None自 3.12 起是 immortal,身份比较即可,无需类型检查函数;Py_True/Py_False同理,见 none.rst 与 bool.rst; - 小整数范围的心智模型:
-5..1024的 int 是缓存单例(long.rst 的 impl-detail,对应 Objects/longobject.c 中的get_small_int路径),这既解释了零分配行为,也意味着该范围内的 int 对象可能被多处共享,不应做就地修改假设。
10. 延伸阅读入口
- 具体对象层总览:Doc/c-api/concrete.rst
- 抽象协议层(对比阅读):Doc/c-api/sequence.rst、Doc/c-api/mapping.rst
- 基础头文件:Include/object.h、Include/longobject.h、Include/dictobject.h
- 类型系统核心实现:Objects/typeobject.c;整数实现:Objects/longobject.c
按此"文档 → 头文件 → 实现"的三件套路径,你可以在当前仓库中逐类深入每个具体对象类型的 C API 契约与底层实现。
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