news 2026/9/7 3:04:09

CPython C API 扩展与嵌入指南:编写、构建并深入你的第一个原生扩展模块

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CPython C API 扩展与嵌入指南:编写、构建并深入你的第一个原生扩展模块

CPython C API 扩展与嵌入指南:编写、构建并深入你的第一个原生扩展模块

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

本文基于 CPython 仓库 Extending and Embedding the Python Interpreter 文档编写,系统讲解如何用 C/C++ 扩展 Python 解释器:从零开始编写第一个 C API 扩展模块、配置构建工具(meson-python)、理解模块导出钩子(PyModExport_*)与槽表机制,并深入源码验证 CPython 动态加载扩展模块的底层流程。读完后,你将掌握完整的扩展模块开发流程,并能结合仓库源码理解错误处理约定、符号导出机制与嵌入(Embedding)场景。

1. 文档定位:扩展与嵌入的完整知识地图

CPython 的 C API(Application Programmers Interface)定义了一组函数、宏和变量,提供对 Python 运行时系统绝大部分方面的访问能力。文档开篇明确了三大主题:

  • 用 C 或 C++ 编写模块扩展 Python 解释器。这些模块能做的事情和 Python 代码一样——定义函数、对象类型和方法——此外还能与原生库交互,或通过避免解释器开销获得更好的性能;
  • 将 Python 解释器嵌入到另一个应用程序中,把 Python 当作扩展语言使用;
  • 如何编译和链接扩展模块,使其能在运行时被解释器动态加载(前提是操作系统支持该特性)。

文档假设读者具备 C 与 Python 的基础知识:非正式的 Python 入门见 教程,语言的形式化定义见 语言参考,现有对象类型、函数和模块的完整文档见 库参考,而完整的 Python/C API 描述则单独整理在 C API 文档。

在 C 源码文件中引入 Python API 的方式很简单——包含头文件Python.h。但文档特别强调了一个重要的可移植性提示:

C 扩展接口是 CPython 特有的,扩展模块不能在其他 Python 实现(如 PyPy、Jython)上工作。在很多情况下,可以避免编写 C 扩展以保留可移植性。例如,如果用途只是调用 C 库函数或系统调用,应考虑使用ctypes模块或 cffi 库,而不是编写自定义 C 代码。这些工具让你在 Python 中编写与 C 代码对接的代码,比编译 C 扩展模块更可移植。

CPython 本身并不附带构建扩展模块的工具,官方推荐使用第三方构建后端(见 C API 工具列表)。文档还指出,教程模块可以作为构建工具的简单测试用例,或作为代码生成器的预期输出样例——这也说明了该文档面向的读者既包括扩展作者,也包括扩展开发工具的作者。

知识地图:文档目录结构

Doc/extending/目录下的文档按学习路径组织:

章节文档内容定位
first-extension-module.rst入门教程:创建第一个 C API 扩展模块
extending.rst中级主题:C API assorted topics(错误与异常等)
newtypes_tutorial.rst教程:定义新的对象类型
newtypes.rst新类型相关进阶话题
building.rst构建扩展模块的一般指南
windows.rstWindows 平台构建指南
embedding.rst将 CPython 运行时嵌入更大的应用程序

其中,“中级主题”部分(错误与异常、新类型、构建)主要面向那些开发扩展工具本身的人,而非推荐普通用户用它来写扩展;“嵌入”部分则讨论相反的场景——不是创建运行在 Python 解释器内部的主应用程序的扩展,而是把 CPython 运行时嵌入到一个更大的应用里。

2. 前置条件与版本约束

进入教程之前,需要明确环境要求(源自 first-extension-module.rst):

  • C 编译器Python 开发头文件。在 Linux 上,头文件通常在python3-dev(Debian/Ubuntu 系)或python3-devel(RHEL 系)包中;
  • 能安装 Python 包的能力。教程使用pippip install),也可以替换为任何能构建并安装基于pyproject.toml项目的工具(如uv pip install);建议在虚拟环境中进行;
  • 目标系统:教程假设 Unix 类系统(包括 macOS 与 Linux)或 Windows,其他系统可能需要调整部分细节(例如系统命令名);
  • 版本注意:本仓库当前版本为 3.16.0a0(见 patchlevel.h),教程使用了CPython 3.15 新增的 API(如模块导出钩子PyModExport_*)以及C11/C++20 语法。如果要创建兼容更早版本 CPython 的扩展,应查阅对应版本文档——例如 CPython 3.14 及以下要求扩展模块定义PyInit_*初始化函数。

教程选择实现一个名为spam的模块,作为 C 标准库函数system的 Python 接口(spam是 Monty Python 粉丝的最爱食物,这是教程的命名彩蛋):

#include <stdlib.h> int system(const char *command);

目标调用形式:

>>> import spam >>> status = spam.system("whoami") User Name >>> status 0

文档同时提醒:system这样的 C 标准库函数在 Python 中已经有现成暴露,生产环境中请使用os.systemsubprocess.run,而不是自己写的模块。选择whoami作为演示命令,是因为它在 Unix 和 Windows 上同名,方便跨平台演示。

3. 教程实战:从零构建spam模块

3.1 从头部文件开始

创建目录并切换进去,然后创建spammodule.c文件(文件名随意,但传统上扩展模块用*module.c后缀;Python 非主语言的项目可能用py_spam.c之类)。文件开头包含两个头文件:

#include <Python.h> #include <stdlib.h> // for system()

关键规则:stdlib.h等标准库头文件必须放在Python.h之后。因为在某些系统上,Python 会定义一些影响标准头文件行为的预处理宏。虽然技术上包含stdlib.h并非必需——Python.h本身就会为自身使用或向后兼容而包含它和若干标准头——但显式包含自己需要的头文件是好习惯。

3.2 配置构建工具(meson-python)

虽然此时扩展还什么都没做,但先编译验证构建工具可用很有价值,方便后续增量开发。教程选用meson-python作为构建后端,它需要两个项目文件。

pyproject.toml

[build-system] build-backend = 'mesonpy' requires = ['meson-python'] [project] # Placeholder project information # (change this before distributing the module) name = 'sampleproject' version = '0'

meson.build

project('sampleproject', 'c') py = import('python').find_installation(pure: false) py.extension_module( 'spam', # name of the importable Python module 'spammodule.c', # the C source file install: true, )

构建并安装当前目录(.)中的项目:

python -m pip -v install .

-v--verbose)选项让 pip 显示编译器输出,开发期间经常有用。两个实用提示:

  • 如果系统没有 pip,先运行python -m ensurepip(最好在虚拟环境中);
  • 每次修改扩展后都要重新运行安装命令——不像 Python,C 有显式的编译步骤。

3.3 第一次导入:观察报错以验证加载机制

编译安装后启动 Python 尝试导入,此时应该失败并抛出:

>>> import spam Traceback (most recent call last): ... ImportError: dynamic module does not define module export function (PyModExport_spam or PyInit_spam)

这个报错本身就是一条宝贵的诊断信息:它证明动态加载机制已经生效,CPython 正在 .so 文件中按符号名查找模块导出函数。这条错误消息正是 CPython 源码 Python/importdl.c 中_PyImport_GetModuleExportHooks()函数生成的:

if (!PyErr_Occurred()) { PyObject *msg; msg = PyUnicode_FromFormat( "dynamic module does not define " "module export function (%s_%s or %s_%s)", info->hook_prefixes->export_prefix, name_buf, info->hook_prefixes->init_prefix, name_buf); ... PyErr_SetImportError(msg, info->name, info->filename); }

从源码结构看,CPython 在加载动态模块时优先查找新式导出钩子符号(PyModExport_<name>),找不到再回退到旧式初始化函数符号(PyInit_<name>)。两种前缀常量定义在 Python/importdl.c:

static const struct hook_prefixes ascii_only_prefixes = { "PyInit", "PyModExport"}; static const struct hook_prefixes nonascii_prefixes = { "PyInitU", "PyModExportU"};

对非 ASCII 模块名,符号名按 PEP 489 规则使用 Punycode 编码,前缀相应变为PyInitU/PyModExportU。加载流程先尝试 export 前缀(找到则返回 2),再尝试 init 前缀(找到则返回 1),都找不到才抛出上述ImportError(见 Python/importdl.c)。

3.4 定义模块导出钩子

错误信息告诉我们 CPython 在寻找“模块导出函数”(module export function,亦称模块导出钩子)。定义方式分两步。

第一步:添加函数原型(放在#include行下方):

PyMODEXPORT_FUNC PyModExport_spam(void);

原型并非严格必需,但某些现代编译器没有它会发出警告——通常添加原型比禁用警告更好。PyMODEXPORT_FUNC宏声明函数的返回类型,并添加使函数在 CPython 加载时可见、可用的特殊链接声明。该宏的定义见 Include/exports.h:

#ifndef PyMODEXPORT_FUNC #define PyMODEXPORT_FUNC _PyINIT_FUNC_DECLSPEC PySlot* #endif

也就是说,导出钩子的返回类型是PySlot*(槽表数组)。而_PyINIT_FUNC_DECLSPEC根据平台展开为extern "C"(C++ 时)加上导出符号修饰:在 Windows/Cygwin 下是__declspec(dllexport)(见 Include/exports.h),在其他平台是__attribute__((visibility("default")))。这些宏还负责区分核心模块与扩展模块的符号可见性——扩展模块的导出钩子必须具有外部链接,CPython 才能通过动态符号查找(dlsym等)定位到它。

第二步:实现函数。先让它返回NULL

PyMODEXPORT_FUNC PyModExport_spam(void) { return NULL; }

重新编译并再次导入,会得到不同的错误:

>>> import spam SystemError: module export hook for module 'spam' failed without setting an exception

仅返回NULL并不是导出钩子的正确行为,CPython 会抱怨。但这恰恰是好消息——它意味着 CPython 已经找到了你的函数!

3.5 槽表(Slot Table):模块身份的声明

导出钩子应该返回创建模块所需的信息。最基础的信息是模块名和 docstring,它们应定义在一个PySlot条目数组中——本质上是键值对。把数组定义在导出钩子之前:

PyABIInfo_VAR(abi_info); static PySlot spam_slots[] = { PySlot_STATIC_DATA(Py_mod_abi, &abi_info), PySlot_STATIC_DATA(Py_mod_name, "spam"), PySlot_STATIC_DATA(Py_mod_doc, "A wonderful module with an example function"), PySlot_END };

逐条说明:

  • PySlot_STATIC_DATA宏用于槽值是“指向常量、静态分配数据的指针”的场景(这里分别是&abi_info"spam"和 docstring)。Py_mod_namePy_mod_doc的取值都是 C 字符串——NUL 结尾、UTF-8 编码的字节数组;
  • PyABIInfo_VAR(abi_info)宏与Py_mod_abi是样板代码(boilerplate),用于防止为不同 Python 版本编译的扩展加载后导致解释器崩溃;
  • PySlot_END是哨兵条目,标记数组结束。忘记它会导致未定义行为
  • 数组声明为static——即在此.c文件外不可见。这是常见主题:CPython 只需要访问导出钩子,所有全局变量和其他函数通常都应该是static,以免与其他扩展冲突(对比PyMODEXPORT_FUNC展开出的导出可见性修饰——整个.c文件中只有导出钩子需要外部链接)。

让导出钩子返回该数组:

PyMODEXPORT_FUNC PyModExport_spam(void) { return spam_slots; }

重新编译测试:

>>> import spam >>> print(spam) <module 'spam' from '/home/encukou/dev/cpython/spam.so'>

你已经拥有了一个扩展模块!用help(spam)可以看到 docstring。

3.6 暴露函数:胶水代码与PyMethodDef

要把 C 函数system直接暴露给 Python,需要写一层胶水代码(glue code),把参数从 Python 对象转换成 C 值,再把 C 返回值转回 Python。最简单的方式之一是METH_O函数——接收两个 Python 对象、返回一个对象。所有 Python 对象无论类型,在 C 中都表示为指向PyObject结构的指针。

在槽数组上方添加这样的函数:

static PyObject * spam_system(PyObject *self, PyObject *arg) { Py_RETURN_NONE; }

暂时忽略参数,用Py_RETURN_NONE宏返回 Python 的None对象(它展开为一个正确返回Nonereturn语句)。重新编译后可能收到spam_system未使用的警告——这是正常的,因为它还没有被加到模块里。

方法定义表(Method Definitions):要把 C 函数暴露给 Python,需要提供PyMethodDef结构中的若干信息:

  • ml_name:Python 函数名;
  • ml_doc:docstring;
  • ml_meth:被调用的 C 函数;
  • ml_flags:描述细节的标记集,例如 Python 参数如何传递给 C 函数。这里用METH_O——匹配spam_system函数签名的标记。

PyMethodDef结构同时用于创建类的方法,因此不存在单独的“PyFunctionDef”。)

由于模块通常要创建多个函数,这些定义要收集在一个数组中,末尾放一个零填充的哨兵:

static PyMethodDef spam_methods[] = { { .ml_name="system", .ml_meth=spam_system, .ml_flags=METH_O, .ml_doc="Execute a shell command.", }, {NULL, NULL, 0, NULL} /* Sentinel */ };

然后向槽表添加Py_mod_methods槽,指向该PyMethodDef数组:

static PySlot spam_slots[] = { PySlot_STATIC_DATA(Py_mod_abi, &abi_info), PySlot_STATIC_DATA(Py_mod_name, "spam"), PySlot_STATIC_DATA(Py_mod_doc, "A wonderful module with an example function"), PySlot_STATIC_DATA(Py_mod_methods, spam_methods), PySlot_END };

重新编译、重启 Python 解释器(让import spam拿到新版本模块),测试:

>>> import spam >>> print(spam.system) <built-in function system> >>> print(spam.system('whoami')) None

此时spam.system还没有真正执行whoami命令,只是返回None。再验证参数个数检查(由METH_O标记指定恰好一个参数):

>>> print(spam.system('too', 'many', 'arguments')) TypeError: spam.system() takes exactly one argument (3 given)

3.7 返回整数:PyLong_FromLong

接下来处理返回值。要让spam.system返回一个数字——Python 的int对象。C API 提供了从 C 的int值创建 Pythonint对象的函数PyLong_FromLong

(函数名可能不太直观:PyLong指 Python 的int类——它最初叫longFromLong则指 C 的long(即long int)类型。)

替换Py_RETURN_NONE

static PyObject * spam_system(PyObject *self, PyObject *arg) { int status = 3; PyObject *result = PyLong_FromLong(status); return result; }

重新编译、重启解释器,确认函数现在返回 3:

>>> import spam >>> spam.system('whoami') 3

3.8 接受字符串参数:PyUnicode_AsUTF8AndSize与错误处理

最后处理函数参数。C 函数spam_system接收两个参数:第一个PyObject *self会被设为spam模块对象(本例无用,忽略);第二个PyObject *arg是用户从 Python 传入的对象,期望是 Python 字符串。

这里存在一个微妙的类型不匹配:Python 的str对象存储 Unicode 文本,而 C 字符串是字节数组。所以需要把数据编码,本例使用 UTF-8。(UTF-8 未必总是适合系统命令,但它是str.encode的默认编码,且 C API 对它有专门支持。)

把 Python 字符串编码为 UTF-8 缓冲区的函数是PyUnicode_AsUTF8AndSize

static PyObject * spam_system(PyObject *self, PyObject *arg) { const char *command = PyUnicode_AsUTF8AndSize(arg, NULL); int status = 3; PyObject *result = PyLong_FromLong(status); return result; }

(名字中PyUnicode指向str类的最初名称unicodeAndSize部分指该函数还能通过输出参数获取缓冲区大小——本例不需要,所以第二个参数传NULL。)

调用成功时,command指向结果 C 字符串——零结尾的字节数组。这个缓冲区由arg对象管理,无需释放,但必须遵守规则

  • 只应在spam_system函数内部使用该缓冲区。函数返回后,arg及其管理的缓冲区可能已被垃圾回收;
  • 不得修改它,因此使用const

若调用不成功PyUnicode_AsUTF8AndSize返回NULL。调用任何 Python C API 时都必须处理这类错误情况。本例中正确的处理方式是:spam_system直接返回NULL

static PyObject * spam_system(PyObject *self, PyObject *arg) { const char *command = PyUnicode_AsUTF8AndSize(arg); if (command == NULL) { return NULL; } int status = 3; PyObject *result = PyLong_FromLong(status); return result; }

这个“失败时返回NULL且不重复设置异常”的模式是整个 C API 错误传播约定的缩影(详见 4.1 节)。测试错误处理——传入非字符串值:

>>> import spam >>> spam.system(3) TypeError: bad argument type for built-in operation

3.9 最终形态:调用system并返回真实结果

剩下就是把system库函数用char *缓冲区调用起来,并用其结果替换3

static PyObject * spam_system(PyObject *self, PyObject *arg) { const char *command = PyUnicode_AsUTF8AndSize(arg); if (command == NULL) { return NULL; } int status = system(command); PyObject *result = PyLong_FromLong(status); return result; }

编译模块、重启 Python、测试。这次会看到whoami命令的输出——你的用户名:

>>> import spam >>> result = spam.system('whoami') User Name >>> result 0

也可以测试其他命令,如lsdir,或一个不存在的命令:

>>> import spam >>> result = spam.system('nonexistent-command') sh: line 1: nonexistent-command: command not found >>> result 32512

完整的spammodule.c源码收录在 Doc/includes/capi-extension/spammodule-01.c(该文件头部注释明确说明需与教程文档保持同步)。一个值得注意的脚注:我们忽略了 Python 字符串可以包含 NUL 字节(会截断 C 字符串)这一事实,即spam.system("foo\0bar")会被当作spam.system("foo")。这可能带来安全问题,所以真正的os.system会检查这种情况并报错。

4. 进阶主题:C API 的核心约定

教程刻意避开了错误处理与引用计数等“重要概念”,它们由 extending.rst(Using the C API: Assorted topics)覆盖。理解这些约定是写出健壮扩展的关键。

4.1 错误与异常(Errors and Exceptions)

Python 解释器中一个重要的约定是:函数失败时,应设置一个异常条件并返回错误值(通常是-1NULL指针)。异常信息存储于解释器线程状态的三个成员中:异常类型、异常实例、traceback 对象——无异常时它们为NULL,否则等价于sys.exc_info()返回的元组的 C 版本。

常用的设置异常的 API:

  • PyErr_SetString:最常用的一个,参数是异常对象和一个 C 字符串。异常对象通常是预定义对象(如PyExc_ZeroDivisionError);C 字符串表示错误原因,会被转换成 Python 字符串对象存为异常的“关联值”;
  • PyErr_SetFromErrno:只接收异常参数,通过检查全局变量errno构造关联值;
  • PyErr_SetObject:最通用的,接收两个对象参数——异常与其关联值。传给这些函数的对象不需要Py_INCREF

错误传播规则(与教程中command == NULL → return NULL的做法呼应):

  • 调用另一个函数g的函数f,在检测到g失败时,f应自己返回错误值(通常NULL-1),而不应再调用PyErr_*函数——g已经调用了。f的调用者同样应向它的调用者返回错误指示而不调用PyErr_*,如此一路向上传播,直到解释器主循环,那里会中止当前执行的 Python 代码并尝试查找程序员指定的异常处理器;
  • 唯一需要调用PyErr_Clear清除异常的场景:你不想把错误交给解释器,而是想完全自己处理(比如重试或假装什么都没发生)。模块可以在某些情况下用另一个PyErr_*函数给出更详细的错误消息,但一般规则是不要这样做,否则会丢失错误原因的信息;
  • 每个失败的malloc调用都必须转换为异常——malloc(或realloc)的直接调用者必须调用PyErr_NoMemory并自行返回失败指示(对象创建函数如PyLong_FromLong已经这样做了,所以该注意事项只针对直接调用malloc的代码);
  • 注意:除PyArg_ParseTuple及其伙伴外,返回整数状态的函数通常成功返回正值或零、失败返回-1,类似 Unix 系统调用;
  • 返回错误指示时,务必清理垃圾——对已创建的对象调用Py_XDECREFPy_DECREF

异常类型选择完全由你决定,但应明智选择:所有内置 Python 异常都有对应的预声明 C 对象(如PyExc_ZeroDivisionError)可直接使用。不要用PyExc_TypeError表示“文件打不开”(那应该是PyExc_OSError);参数列表有问题时PyArg_ParseTuple通常抛PyExc_TypeError;参数值超出范围或必须满足其他条件时用PyExc_ValueError合适。也可以为模块定义独有的新异常,最简单的方式是在文件开头声明一个 static 全局对象变量,并在模块初始化时用PyErr_NewException初始化它。

4.2 模块导出机制的源码级印证

教程中的每个报错,都能在 CPython 加载器源码中找到对应逻辑,这一印证帮助我们把“文档行为”上升为“实现事实”:

  1. 符号查找顺序_PyImport_GetModuleExportHooks先用export_prefixPyModExport)查符号,成功后返回 2;再退回init_prefixPyInit),成功后返回 1;两者皆无则抛出含两个候选符号名的ImportError。因此旧式PyInit_*模块在新版本上仍可加载,而新式钩子返回的PySlot*槽表才是 3.15+ 的推荐路径;
  2. 非 ASCII 模块名:模块短名(最后一个.之后的部分)先尝试 ASCII 编码,失败则按 PEP 489 转 Punycode,符号前缀切换为PyInitU/PyModExportU(见 Python/importdl.c);
  3. 钩子返回NULL的处理:若导出钩子返回NULL且未设置异常,加载器判定为“failed without setting an exception”并抛SystemError——这正是教程中观察到的第二个报错;
  4. 符号可见性PyMODEXPORT_FUNC经由 Include/exports.h 展开,保证钩子在所有目标平台上都以外部链接可见(Windows 用__declspec(dllexport),GCC/Clang 平台用visibility("default")),与教程中“其他全局变量和函数都应为static”的告诫形成对照。

5. 其他构建工具与直接编译

教程正文使用 meson-python,但附录提供了替代路径(源自 first-extension-module.rst 的 “Appendix: Other build tools”):

5.1 缺失PyInit函数的临时对策

如果你的构建工具输出抱怨缺少PyInit_spam,可以临时添加:

// A workaround void *PyInit_spam(void) { return NULL; }

这是旧式初始化函数(initialization function,CPython 3.14 及以下的扩展模块要求定义)的垫片(shim)。当前 CPython 不需要它,但某些构建工具可能仍然假设所有扩展模块都要定义它。使用这个对策后,你会得到SystemError: initialization of spam failed without raising an exception而不是ImportError: dynamic module does not define module export function

5.2 直接调用编译器(仅限特定系统自用场景)

使用第三方构建工具被强烈推荐,因为它会处理平台与 Python 安装的诸多细节、生成扩展的命名,以及日后的分发。但如果你只为特定系统自己构建扩展,也可以直接运行编译器——方式是系统相关的,需自行解决可能出现的问题。

以 Linux 为例,Python 开发包可能附带python3-config命令,可打印所需的编译旗标。使用它时,确认它对应于你要用来加载模块的 CPython 解释器,然后:

gcc --shared $(python3-config --cflags --ldflags) spammodule.c -o spam.so

这会生成spam.so文件,需要把它放到sys.path上的某个目录中。

6. 嵌入(Embedding)与后续学习路径

Doc/extending/的最后一个主题方向是反向场景:不是把 C 代码塞进 Python 解释器,而是把 CPython 运行时嵌入到一个更大的应用程序中,让 Python 作为该应用的脚本/扩展语言。embedding.rst 覆盖成功完成此事所需的一些细节。

综合学习路径建议如下:

  1. 先完成 first-extension-module.rst 教程,跑通spam模块全流程;
  2. 精读 extending.rst 的错误与异常等中级主题——它们决定扩展在生产环境下的健壮性;
  3. 需要自定义对象类型时,学习 newtypes_tutorial.rst 与 newtypes.rst;
  4. 跨平台分发前,参考 building.rst 的一般构建指南与 windows.rst 的 Windows 专项指南;
  5. 应用形态为宿主程序时,转向 embedding.rst;
  6. API 细节随时查 Doc/c-api/ 中的完整 C API 文档。

7. 关键 API 速查表

API用途失败行为/要点
PyMODEXPORT_FUNC声明模块导出钩子,返回PySlot*定义见 Include/exports.h
PyModExport_<name>()3.15+ 新式模块导出钩子返回NULL且不设异常会触发SystemError
PyInit_<name>()旧式初始化函数(3.14 及以下要求)新式钩子存在时优先被查找的是PyModExport_*
PySlot/PySlot_STATIC_DATA模块槽表键值对数组必须以PySlot_END结尾
Py_mod_abi+PyABIInfo_VARABI 版本校验样板代码防止版本不匹配的扩展崩溃解释器
Py_mod_name/Py_mod_doc模块名与 docstring取值为 NUL 结尾 UTF-8 C 字符串
Py_mod_methods挂载PyMethodDef数组数组以{NULL, NULL, 0, NULL}哨兵结尾
PyMethodDef描述一个绑定方法/函数ml_name/ml_doc/ml_meth/ml_flags四要素
METH_O函数标记恰好接收一个参数,多余参数抛TypeError
PyLong_FromLongClong→ Pythonint失败返回NULL并设PyErr_NoMemory等异常
PyUnicode_AsUTF8AndSizestr→ UTF-8 只读 C 缓冲区失败返回NULL;缓冲区生命周期绑定源对象,不得修改
Py_RETURN_NONE返回None的宏展开为正确的return语句
PyErr_SetString/PyErr_SetFromErrno/PyErr_SetObject设置异常参数对象无需Py_INCREF
PyErr_Clear清除异常仅在模块要自行完全处理错误时调用
PyErr_NoMemorymalloc失败时设置内存错误malloc/realloc直接调用者必须处理

8. 小结

Doc/extending/文档给出了 CPython 扩展开发的完整坐标系:C API 经Python.h暴露运行时能力,但这是 CPython 专属接口、不跨实现可移植;构建交由第三方后端(教程选定 meson-python);3.15+ 起模块通过PyModExport_*导出钩子返回PySlot槽表来声明模块身份与方法(源码验证见Python/importdl.c的查找顺序与错误消息生成逻辑);错误处理遵循“设置一次、逐层返回错误值”的约定。教程中的spam模块(完整源码在 Doc/includes/capi-extension/spammodule-01.c)恰好覆盖了“头文件 → 构建配置 → 导出钩子 → 槽表 → 方法表 → 参数编码 → 返回值转换 → 错误传播”的全链路,是任何构建工具与代码生成器的天然基准测试用例。掌握这条主线后,新类型定义、平台化构建与解释器嵌入便都是在此骨架上的自然延伸。

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

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

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

加扰与解扰:从伪随机序列到时钟恢复的工程实战解析

简介&#xff1a;面向数字通信与FPGA开发者的VHDL加扰与解扰工程包&#xff0c;完整演示了从算法建模到硬件验证的流程。加扰用于将连续1/0序列随机化&#xff0c;降低信道中的自相关干扰&#xff1b;解扰则在接收端恢复原始数据&#xff0c;是数字电视、LTE/5G及卫星通信的常见…

作者头像 李华
网站建设 2026/9/7 3:03:07

C盘清理实战:从空间分析到自动化脚本,Windows系统优化完整指南

C盘红色条又快撑满的时候&#xff0c;很多人的第一反应是下载一个“C盘清理神器”。这类工具在搜索结果里非常多&#xff0c;标题也基本都会带上“系统优化、一键清理、完全免费”这些词。说实话&#xff0c;Windows环境下确实需要定期做C盘维护&#xff0c;但真正该做的第一件…

作者头像 李华
网站建设 2026/9/7 3:02:55

i.MX6ULL平台Linux驱动:Platform机制与设备树匹配全解析

1. 先聊聊为什么Linux驱动必须搞懂Platform机制做了几个月的裸机驱动&#xff0c;或者刚写完几个字符设备驱动的新手&#xff0c;大概率会遇到一个困惑&#xff1a;我在x86的虚拟机上写的hello驱动&#xff0c;怎么换到i.MX6ULL这种ARM板卡上就跑不通&#xff1f;原因当然不只是…

作者头像 李华
网站建设 2026/9/7 3:01:46

2026 研发团队协作优化方案:AI 自动生成交接文档与 PR 提交说明

开发者的核心价值本应聚焦业务逻辑设计与核心功能研发&#xff0c;但现实中&#xff0c;超过六成的工作时间被源码梳理、架构拆解、文档编写、缺陷排查等重复性事务挤占。借助面向本地项目的 AI 智能助手承接基础事务&#xff0c;可将新项目摸底周期从数天压缩至数分钟&#xf…

作者头像 李华