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

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

2026-09-06 15:27:01作者:韦蓉瑛

本文基于 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.rst Windows 平台构建指南
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 值创建 Python int 对象的函数 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.rstnewtypes.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_VAR ABI 版本校验样板代码 防止版本不匹配的扩展崩溃解释器
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_FromLong C long → Python int 失败返回 NULL 并设 PyErr_NoMemory 等异常
PyUnicode_AsUTF8AndSize str → UTF-8 只读 C 缓冲区 失败返回 NULL;缓冲区生命周期绑定源对象,不得修改
Py_RETURN_NONE 返回 None 的宏 展开为正确的 return 语句
PyErr_SetString / PyErr_SetFromErrno / PyErr_SetObject 设置异常 参数对象无需 Py_INCREF
PyErr_Clear 清除异常 仅在模块要自行完全处理错误时调用
PyErr_NoMemory malloc 失败时设置内存错误 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)恰好覆盖了“头文件 → 构建配置 → 导出钩子 → 槽表 → 方法表 → 参数编码 → 返回值转换 → 错误传播”的全链路,是任何构建工具与代码生成器的天然基准测试用例。掌握这条主线后,新类型定义、平台化构建与解释器嵌入便都是在此骨架上的自然延伸。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388