首页
/ CPython C API 具体对象层详解:如何安全地创建、检查与操作 Python 内置对象

CPython C API 具体对象层详解:如何安全地创建、检查与操作 Python 内置对象

2026-09-05 15:48:39作者:申梦珏Efrain

本篇围绕 CPython 官方文档的 Concrete Objects Layer(具体对象层)章节展开,系统梳理该层 C API 的"对象族谱"式组织结构、逐类调用前的类型检查纪律与 NULL 传入风险,并结合 Objects/ 目录下 PyType_TypePyLong_FromLong 等源码实现,说明小整数缓存、immortal 单例等底层机制,帮助你编写既正确又高效的 C 扩展模块与嵌入层代码。

1. 具体对象层(Concrete Objects Layer)的定位

concrete.rst 描述的"具体对象层"是 Python C API 中针对特定 Python 对象类型的那一组函数。它与抽象层(PySequence_*PyMapping_* 等基于协议的通用接口)形成对比:抽象层可以操作任何实现了对应协议的对象,而具体层函数只对某一确切类型有效。

文档开篇给出了两条必须牢记的使用纪律:

  1. 先做类型检查,再调用具体层函数。向具体层函数传入错误类型的对象"不是一个好主意"。如果你从 Python 程序接收到一个对象且不确定其类型,必须先执行类型检查——文档给出的例子是:要确认对象是字典,先用 PyDict_Check
  2. NULL 不会被拦截。原文的警告框明确指出:本章描述的函数会仔细检查传入对象的类型,但其中许多并不检查传入的是不是 NULL 而非有效对象;允许传入 NULL 可能导致内存访问违规并直接终止解释器。因此从 Python 侧拿到的每个对象都应在使用前先判空。

整个章节按 Python 对象类型的"族谱"(family tree)组织,从上至下分为五大部分,每一部分对应一组子章节:

部分 子章节(对应文档)
Fundamental Objects(基础对象) Type ObjectsThe None Object
Numeric Objects(数值对象) IntegerBooleanFloatComplex
Sequence Objects(序列对象) BytesByte ArrayUnicodeTupleList
Container Objects(容器对象) DictionarySet
Function Objects(函数对象) FunctionMethodCellCode
Other Objects(其他对象) FileModuleIteratorDescriptorSliceMemoryViewPickleBufferWeakRefCapsuleSentinelFrameGeneratorCoroContextVarType Hints
C API for extension modules(类型专属模块 API) CursesDatetime

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 对象章节非常短但信息密度高:

  • NonePyTypeObject 没有在 Python/C API 中直接暴露;
  • 因为 None 是单例,用 C 的 ==对象身份比较就足够了,所以也不存在 PyNone_Check 这类函数;
  • Py_NonePyObject *):表示"无值"的 Python None 对象,没有任何方法,并且自 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 为 -51024 之间的所有整数保留了一个对象数组,创建该范围内的 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_FromUnsignedLongPyLong_FromSsize_tPyLong_FromSize_tPyLong_FromLongLongPyLong_FromUnsignedLongLong 等覆盖各种 C 整数宽度的构造函数(3.13 起另有 PyLong_FromInt32 等),以及对应的 PyLong_As* 提取函数族。

3.2 其余数值类型

  • Boolean ObjectsPyBoolObjectPyBool_Type;注意 bool 是 int 的子类,bool 对象只有 Py_True/Py_False 两个单例;
  • Float ObjectsPyFloatObjectPyFloat_FromDoublePyFloat_AsDouble 等;
  • Complex ObjectsPyComplexObjectPyComplex_FromCComplex 等。

4. 序列对象(Sequence Objects)

concrete.rst 在该部分开篇说明:序列对象的通用操作(长度、索引、切片等基于 PySequence_* 协议)已在前一章(sequence.rst)讨论;本节处理 Python 语言内置的序列具体类型。文档列出的五类及其文档为:

从源码结构看,每一类都遵循相同的"文档—头文件—实现"三件套布局,便于按类型定位实现:

类型 文档 公开头文件 实现文件
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 开头示例的来源:

  • PyDictObjectPyObject 子类型,表示 Python 字典;
  • PyDict_Type:与 Python 层 dict 相同的类型对象;
  • PyDict_Check(PyObject *p)p 是 dict 或 dict 子类型的实例时为真,总是成功——这正是主文档中"不确定类型时先检查"的标准示范;
  • PyDict_CheckExact(PyObject *p):仅当 p 是 dict 本身(不含子类型实例)时为真;
  • PyDict_New():返回新的空字典,失败返回 NULL

Set Objects 章节则覆盖 PySetObject/PyFrozenSetObjectPySet_New 等集合操作 API。实现上分别位于 dictobject.csetobject.c

6. 函数对象(Function Objects)

Function Objects 部分收录了"可调用物"及其支撑结构对应的四篇子文档:

7. 其他对象(Other Objects)

Other Objects 部分是族谱中"杂项但高频"的一层,concrete.rst 依次列出 15 个子章节,每一篇都遵循"类型结构 → 类型对象 → Check 函数 → 构造/操作函数"的相同编排:

8. 类型专属模块 API:curses 与 datetime

concrete.rst 的最后一部分"C API for extension modules"收录了两个绑定到特定 Python 类型的模块级 C API

这两篇与其他部分的区别在于:它们不是解释器核心内置类型的 API,而是标准库模块对外暴露的、与其自定义类型绑定的 C 接口。

9. 工程实践要点:把文档纪律落到代码里

综合 concrete.rst 的警告与各子文档的函数契约,C 扩展中使用具体对象层 API 的安全模式可以归纳为:

  1. 来源不可信则必检查:从 Python 侧取得的对象,先 PyXxx_Check(接受子类型)或 PyXxx_CheckExact(只要精确类型),再调用具体层函数;
  2. NULL 自负:具体层函数不替你挡 NULL,调用链上游保证非空,否则可能直接触发内存访问违规并终止解释器;
  3. 区分"总是成功"与"可失败"的函数:各 *_Check 系列文档均注明"This function always succeeds",而 PyXxx_From* 构造函数以 NULL 表示失败、PyXxx_As* 提取函数以 -1(或 -1/-1.0 等类型化错误值)表示失败且需配合 PyErr_Occurred 消歧(long.rst 明确指出了该歧义);
  4. 单例对象特殊对待Py_None 自 3.12 起是 immortal,身份比较即可,无需类型检查函数;Py_True/Py_False 同理,见 none.rstbool.rst
  5. 小整数范围的心智模型-5..1024 的 int 是缓存单例(long.rst 的 impl-detail,对应 Objects/longobject.c 中的 get_small_int 路径),这既解释了零分配行为,也意味着该范围内的 int 对象可能被多处共享,不应做就地修改假设。

10. 延伸阅读入口

按此"文档 → 头文件 → 实现"的三件套路径,你可以在当前仓库中逐类深入每个具体对象类型的 C API 契约与底层实现。

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