29 KiB
构建 Python C 扩展模块
有几种方法可以扩展 Python 的功能。其中之一就是用 C 或者 C++ 写你的 Python 模块。这个过程可以提高性能,更好地访问 C 库函数和系统调用。在本教程中,您将了解如何使用 Python API 编写 Python C 扩展模块。
您将学习如何:
- 从 Python 内部调用 C 函数
- 从 Python 向 C 传递参数,并相应地解析它们
- 从 C 代码中引发异常,并在 C 中创建自定义 Python 异常
- 在 C 中定义全局常量,并使它们在 Python 中可访问
- 测试、打包和分发您的 Python C 扩展模块
免费下载: 从 Python 技巧中获取一个示例章节:这本书用简单的例子向您展示了 Python 的最佳实践,您可以立即应用它来编写更漂亮的+Python 代码。
扩展你的 Python 程序
Python 的一个鲜为人知但非常强大的特性是它能够调用用 C 或 C++等编译语言定义的函数和库。这允许您将程序的功能扩展到 Python 的内置特性之外。
有许多语言可供选择来扩展 Python 的功能。那么,为什么要用 C 呢?以下是您可能决定构建 Python C 扩展模块的几个原因:
-
实现新的内置对象类型:可以用 C 编写一个 Python 类,然后实例化并扩展 Python 本身的类。这样做的原因可能有很多,但更多的时候,性能是驱动开发人员转向 c 的主要原因。这种情况很少见,但知道 Python 可以扩展到什么程度是很好的。
-
调用 C 库函数和系统调用:很多编程语言都提供了最常用的系统调用的接口。尽管如此,可能还有其他较少使用的系统调用只能通过 c 来访问。Python 中的
os模块就是一个例子。
这并不是一个详尽的列表,但是它给了您在使用 C 或任何其他语言扩展 Python 时可以做什么的要点。
要用 C 语言编写 Python 模块,你需要使用 Python API ,它定义了允许 Python 解释器调用你的 C 代码的各种函数、宏和变量。所有这些工具和更多的工具都打包在 Python.h头文件中。
用 C 编写 Python 接口
在本教程中,您将为一个 C 库函数编写一个小包装器,然后您将从 Python 中调用它。自己实现一个包装器会让你更好地了解何时以及如何使用 C 来扩展你的 Python 模块。
理解fputs()
是您将要包装的 C 库函数:
int fputs(const char *, FILE *)
该函数有两个参数:
const char *是一个字符数组。FILE *是一个文件流指针。
fputs()将字符数组写入文件流指定的文件,并返回一个非负值。如果操作成功,则该值将表示写入文件的字节数。如果有错误,那么它返回EOF。你可以在手册页条目中阅读更多关于这个 C 库函数及其其他变体的信息。
编写 C 函数为fputs()
这是一个基本的 C 程序,使用fputs()将字符串写入文件流:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { FILE *fp = fopen("write.txt", "w"); fputs("Real Python!", fp); fclose(fp); return 1; }
这段代码可以总结如下:
- 打开文件
write.txt。 - 将字符串
"Real Python!"写入文件。
**注意:**本文中的 C 代码应该构建在大多数系统上。它已经在 GCC 上测试过,没有使用任何特殊的标志。
在下一节中,您将为这个 C 函数编写一个包装器。
包装fputs()
在解释其工作原理之前看到完整的代码可能有点奇怪。然而,花一点时间检查最终产品将补充您在以下部分的理解。下面的代码块显示了 C 代码的最终包装版本:
1#include <Python.h> 2
3static PyObject *method_fputs(PyObject *self, PyObject *args) { 4 char *str, *filename = NULL; 5 int bytes_copied = -1; 6
7 /* Parse arguments */ 8 if(!PyArg_ParseTuple(args, "ss", &str, &filename)) { 9 return NULL; 10 } 11
12 FILE *fp = fopen(filename, "w"); 13 bytes_copied = fputs(str, fp); 14 fclose(fp); 15
16 return PyLong_FromLong(bytes_copied); 17}
这段代码引用了三个在Python.h中定义的对象结构:
PyObjectPyArg_ParseTuple()PyLong_FromLong()
这些用于 Python 语言的数据类型定义。现在,您将逐一了解它们。
PyObject
PyObject是一个对象结构,用于为 Python 定义对象类型。所有 Python 对象共享少量使用PyObject结构定义的字段。所有其他对象类型都是这种类型的扩展。
PyObject告诉 Python 解释器将指向一个对象的指针视为一个对象。例如,将上述函数的返回类型设置为PyObject定义了 Python 解释器所需的公共字段,以便将其识别为有效的 Python 类型。
再看一下 C 代码的前几行:
1static PyObject *method_fputs(PyObject *self, PyObject *args) { 2 char *str, *filename = NULL; 3 int bytes_copied = -1; 4
5 /* Snip */
在第 2 行,声明希望从 Python 代码中接收的参数类型:
char *str是你要写入文件流的字符串。char *filename是要写入的文件的名称。
PyArg_ParseTuple()
PyArg_ParseTuple()将从 Python 程序接收的参数解析成局部变量:
1static PyObject *method_fputs(PyObject *self, PyObject *args) { 2 char *str, *filename = NULL; 3 int bytes_copied = -1; 4
5 /* Parse arguments */ 6 if(!PyArg_ParseTuple(args, "ss", &str, &filename)) { 7 return NULL; 8 } 9
10 /* Snip */
如果你看第 6 行,你会看到PyArg_ParseTuple()有以下参数:
-
args属于PyObject类型。 -
"ss"是指定要解析的参数的数据类型的格式说明符。(你可以查看官方文档以获得完整参考。) -
&str和&filename是指向局部变量的指针,解析后的值将被分配给这些变量。
失败时,PyArg_ParseTuple()评估为false。如果失败,那么函数将返回NULL并且不再继续。
fputs()
正如您之前看到的,fputs()有两个参数,其中一个是FILE *对象。因为您不能使用 C 中的 Python API 解析 Python textIOwrapper对象,所以您必须使用一种变通方法:
1static PyObject *method_fputs(PyObject *self, PyObject *args) { 2 char *str, *filename = NULL; 3 int bytes_copied = -1; 4
5 /* Parse arguments */ 6 if(!PyArg_ParseTuple(args, "ss", &str, &filename)) { 7 return NULL; 8 } 9
10 FILE *fp = fopen(filename, "w"); 11 bytes_copied = fputs(str, fp); 12 fclose(fp); 13
14 return PyLong_FromLong(bytes_copied); 15}
下面是这段代码的详细内容:
- **在第 10 行,**你正在传递你将用来创建一个
FILE *对象的文件名,并把它传递给函数。 - **在第 11 行,**你用下面的参数调用
fputs():str是你要写入文件的字符串。fp是您在第 10 行定义的FILE *对象。
然后将fputs()的返回值存储在bytes_copied中。这个整数变量将被返回给 Python 解释器中的fputs()调用。
PyLong_FromLong(bytes_copied)
PyLong_FromLong()返回一个PyLongObject,在 Python 中表示整数对象。您可以在 C 代码的最末尾找到它:
1static PyObject *method_fputs(PyObject *self, PyObject *args) { 2 char *str, *filename = NULL; 3 int bytes_copied = -1; 4
5 /* Parse arguments */ 6 if(!PyArg_ParseTuple(args, "ss", &str, &filename)) { 7 return NULL; 8 } 9
10 FILE *fp = fopen(filename, "w"); 11 bytes_copied = fputs(str, fp); 12 fclose(fp); 13
14 return PyLong_FromLong(bytes_copied); 15}
第 14 行为bytes_copied生成一个PyLongObject,这是在 Python 中调用函数时返回的变量。你必须从你的 Python C 扩展模块返回一个PyObject*给 Python 解释器。
编写初始化函数
您已经编写了构成 Python C 扩展模块核心功能的代码。但是,仍然有一些额外的函数是启动和运行您的模块所必需的。您需要编写模块及其包含的方法的定义,如下所示:
static PyMethodDef FputsMethods[] = { {"fputs", method_fputs, METH_VARARGS, "Python interface for fputs C library function"}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef fputsmodule = { PyModuleDef_HEAD_INIT, "fputs", "Python interface for the fputs C library function", -1, FputsMethods };
这些函数包括 Python 解释器将使用的模块的元信息。让我们浏览一下上面的每个结构,看看它们是如何工作的。
PyMethodDef
为了调用在你的模块中定义的方法,你需要首先告诉 Python 解释器它们。为此,可以使用PyMethodDef。这是一个有 4 个成员的结构,代表模块中的一个方法。
理想情况下,在您的 Python C 扩展模块中会有不止一个您希望可以从 Python 解释器中调用的方法。这就是为什么您需要定义一组PyMethodDef结构:
static PyMethodDef FputsMethods[] = { {"fputs", method_fputs, METH_VARARGS, "Python interface for fputs C library function"}, {NULL, NULL, 0, NULL} };
该结构的每个成员都包含以下信息:
-
"fputs"是用户为了调用这个特定功能而写的名字。 -
method_fputs是要调用的 C 函数的名称。 -
METH_VARARGS是一个标志,告诉解释器该函数将接受两个PyObject*类型的参数:self是模块对象。args是一个包含函数实际参数的元组。如前所述,这些参数是使用PyArg_ParseTuple()解包的。
-
最后一个字符串是表示方法 docstring 的值。
PyModuleDef
正如PyMethodDef保存关于 Python C 扩展模块中方法的信息一样,PyModuleDef结构保存关于模块本身的信息。它不是结构的数组,而是用于模块定义的单个结构:
static struct PyModuleDef fputsmodule = { PyModuleDef_HEAD_INIT, "fputs", "Python interface for the fputs C library function", -1, FputsMethods };
此结构中共有 9 个成员,但并非所有成员都是必需的。在上面的代码块中,您初始化了以下五个:
-
PyModuleDef_HEAD_INIT是类型PyModuleDef_Base的成员,建议只有这一个值。 -
"fputs"是你的 Python C 扩展模块的名字。 -
字符串是代表你的模块 docstring 的值。您可以使用
NULL没有 docstring,或者您可以通过传递一个const char *来指定一个 docstring,如上面的代码片段所示。它属于Py_ssize_t类型。您还可以使用PyDoc_STRVAR()为您的模块定义一个 docstring。 -
-1是存储你的程序状态所需的内存量。当您的模块在多个子解释器中使用时,这很有帮助,它可以有以下值:- 负值表示这个模块不支持子解释器。
- 非负值启用模块的重新初始化。它还指定了要在每个子解释器会话上分配的模块的内存需求。 T3】
-
FputsMethods是指你的方法表。这是您之前定义的PyMethodDef结构的数组。
有关更多信息,请查看关于 PyModuleDef 的官方 Python 文档。
PyMODINIT_FUNC
现在您已经定义了 Python C 扩展模块和方法结构,是时候使用它们了。当 Python 程序第一次导入你的模块时,它会调用PyInit_fputs():
PyMODINIT_FUNC PyInit_fputs(void) { return PyModule_Create(&fputsmodule); }
PyMODINIT_FUNC当被声明为函数返回类型时,隐含地做 3 件事:
- 它隐式设置函数的返回类型为
PyObject*。 - 它声明任何特殊的链接。
- 它将函数声明为 extern“C”。如果您使用 C++,它会告诉 C++编译器不要对符号进行名称管理。
PyModule_Create()将返回一个类型为PyObject *的新模块对象。对于参数,您将传递您之前已经定义的方法结构的地址,fputsmodule。
**注意:**在 Python 3 中,你的 init 函数必须返回一个PyObject *类型。然而,如果您使用 Python 2,那么PyMODINIT_FUNC将函数返回类型声明为void。
将所有这些放在一起
现在,您已经编写了 Python C 扩展模块的必要部分,让我们后退一步,看看它们是如何组合在一起的。下图显示了模块的组件以及它们如何与 Python 解释器交互:
当您导入 Python C 扩展模块时,PyInit_fputs()是第一个被调用的方法。然而,在将引用返回给 Python 解释器之前,该函数会对PyModule_Create()进行后续调用。这将初始化结构PyModuleDef和PyMethodDef,它们保存了关于你的模块的元信息。准备好它们是有意义的,因为您将在 init 函数中使用它们。
一旦完成,对模块对象的引用最终返回给 Python 解释器。下图显示了模块的内部流程:
由PyModule_Create()返回的模块对象有一个对模块结构PyModuleDef的引用,模块结构又有一个对方法表PyMethodDef的引用。当您调用 Python C 扩展模块中定义的方法时,Python 解释器使用模块对象及其携带的所有引用来执行特定的方法。(虽然这并不完全是 Python 解释器在幕后处理事情的方式,但它会让您了解它是如何工作的。)
同样,您可以访问模块的各种其他方法和属性,如模块 docstring 或方法 docstring。这些是在它们各自的结构中定义的。
现在您已经知道当您从 Python 解释器调用fputs()时会发生什么了。解释器使用你的模块对象以及模块和方法引用来调用方法。最后,让我们看看解释器如何处理 Python C 扩展模块的实际执行:
一旦method_fputs()被调用,程序执行以下步骤:
- 用
PyArg_ParseTuple()解析您从 Python 解释器传递的参数 - 将这些参数传递给构成模块核心的 C 库函数
fputs() - 使用
PyLong_FromLong返回来自fputs()的值
要查看代码中的这些相同步骤,再看一下method_fputs():
1static PyObject *method_fputs(PyObject *self, PyObject *args) { 2 char *str, *filename = NULL; 3 int bytes_copied = -1; 4
5 /* Parse arguments */ 6 if(!PyArg_ParseTuple(args, "ss", &str, &filename)) { 7 return NULL; 8 } 9
10 FILE *fp = fopen(filename, "w"); 11 bytes_copied = fputs(str, fp); 12 fclose(fp); 13
14 return PyLong_FromLong(bytes_copied); 15}
概括地说,您的方法将解析传递给模块的参数,将它们发送给fputs(),并返回结果。
打包您的 Python C 扩展模块
在导入新模块之前,首先需要构建它。您可以通过使用 Python 包distutils来实现这一点。
你需要一个名为setup.py的文件来安装你的应用程序。对于本教程,您将关注 Python C 扩展模块特有的部分。对于完整的初级读本,请查看如何向 PyPI 发布开源 Python 包。
您的模块的最小setup.py文件应该如下所示:
from distutils.core import setup, Extension
def main():
setup(name="fputs",
version="1.0.0",
description="Python interface for the fputs C library function",
author="<your name>",
author_email="your_email@gmail.com",
ext_modules=[Extension("fputs", ["fputsmodule.c"])])
if __name__ == "__main__":
main()
上面的代码块显示了传递给setup()的标准参数。仔细看看最后一个位置参数,ext_modules。这需要一个Extensions类的对象列表。Extensions类的对象描述了安装脚本中的单个 C 或 C++扩展模块。这里,您将两个关键字参数传递给它的构造函数,即:
name是模块的名称。[filename]是源代码文件的路径列表,相对于安装脚本。
构建您的模块
既然已经有了setup.py文件,就可以用它来构建 Python C 扩展模块了。强烈建议您使用虚拟环境来避免与您的 Python 环境冲突。
导航到包含setup.py的目录并运行以下命令:
$ python3 setup.py install
这个命令将在当前目录中编译并安装 Python C 扩展模块。如果有任何错误或警告,那么你的程序将抛出它们。确保在尝试导入模块之前修复这些问题。
默认情况下,Python 解释器使用clang来编译 C 代码。如果您想使用gcc或任何其他 C 编译器来完成这项工作,那么您需要相应地设置CC环境变量,要么在设置脚本中,要么直接在命令行中。例如,您可以告诉 Python 解释器使用gcc以这种方式编译和构建您的模块:
$ CC=gcc python3 setup.py install
然而,如果clang不可用,Python 解释器将自动退回到gcc。
运行您的模块
现在一切都准备好了,是时候看看您的模块的运行情况了!一旦成功构建,启动解释器来测试运行 Python C 扩展模块:
>>> import fputs
>>> fputs.__doc__
'Python interface for the fputs C library function'
>>> fputs.__name__
'fputs'
>>> # Write to an empty file named `write.txt`
>>> fputs.fputs("Real Python!", "write.txt")
13
>>> with open("write.txt", "r") as f:
>>> print(f.read())
'Real Python!'
您的函数按预期执行!你传递一个字符串"Real Python!"和一个文件来写这个字符串,write.txt。对fputs()的调用返回写入文件的字节数。您可以通过打印文件内容来验证这一点。
还记得您是如何将某些参数传递给PyModuleDef和PyMethodDef结构的。从这个输出中可以看到,Python 使用这些结构来分配函数名和文档字符串等内容。
至此,您已经有了模块的基本版本,但是您还可以做更多的事情!您可以通过添加自定义异常和常量来改进您的模块。
引发异常
Python 异常与 C++异常非常不同。如果您想从 C 扩展模块中引发 Python 异常,那么您可以使用 Python API 来实现。Python API 为异常引发提供的一些函数如下:
| 功能 | 描述 |
|---|---|
PyErr_SetString(PyObject *type, |
|
| T1】 | 接受两个参数:指定异常类型的类型参数和显示给用户的定制消息 |
PyErr_Format(PyObject *type, |
|
| T1】 | 接受两个参数:一个指定异常类型的PyObject *类型参数,以及一个显示给用户的格式化定制消息 |
PyErr_SetObject(PyObject *type, |
|
| T1】 | 接受两个类型都为PyObject *的参数:第一个指定异常的类型,第二个将任意 Python 对象设置为异常值 |
您可以使用其中任何一个来引发异常。但是,使用哪一种以及何时使用完全取决于您的需求。Python API 将所有的标准异常预定义为PyObject类型。
从 C 代码中引发异常
虽然您不能在 C 中引发异常,但是 Python API 将允许您从 Python C 扩展模块中引发异常。让我们通过在代码中添加PyErr_SetString()来测试这个功能。每当要写入的字符串长度小于 10 个字符时,这将引发异常:
1static PyObject *method_fputs(PyObject *self, PyObject *args) { 2 char *str, *filename = NULL; 3 int bytes_copied = -1; 4
5 /* Parse arguments */ 6 if(!PyArg_ParseTuple(args, "ss", &str, &fd)) { 7 return NULL; 8 } 9
10 if (strlen(str) < 10) { 11 PyErr_SetString(PyExc_ValueError, "String length must be greater than 10"); 12 return NULL; 13 } 14
15 fp = fopen(filename, "w"); 16 bytes_copied = fputs(str, fp); 17 fclose(fp); 18
19 return PyLong_FromLong(bytes_copied); 20}
在这里,您在解析参数之后和调用fputs()之前立即检查输入字符串的长度。如果用户传递的字符串少于 10 个字符,那么您的程序将发出一个带有自定义消息的ValueError。一旦出现异常,程序执行就会停止。
注意method_fputs()在引发异常后如何返回NULL。这是因为每当您使用PyErr_*()引发异常时,它会自动在异常表中设置一个内部条目并返回它。不要求调用函数随后再次设置该条目。因此,调用函数返回一个指示失败的值,通常是NULL或-1。(这也应该解释了为什么在使用PyArg_ParseTuple()解析method_fputs()中的参数时需要返回NULL。)
引发自定义异常
您还可以在 Python C 扩展模块中引发自定义异常。然而,事情有点不同。以前,在PyMODINIT_FUNC中,你只是简单地返回由PyModule_Create返回的实例,然后就结束了。但是为了让模块的用户可以访问您的自定义异常,您需要在返回它之前将您的自定义异常添加到模块实例中:
static PyObject *StringTooShortError = NULL; PyMODINIT_FUNC PyInit_fputs(void) { /* Assign module value */ PyObject *module = PyModule_Create(&fputsmodule); /* Initialize new exception object */ StringTooShortError = PyErr_NewException("fputs.StringTooShortError", NULL, NULL); /* Add exception object to your module */ PyModule_AddObject(module, "StringTooShortError", StringTooShortError); return module; }
和前面一样,首先创建一个模块对象。然后使用PyErr_NewException创建一个新的异常对象。这将采用一个形式为module.classname的字符串作为您希望创建的异常类的名称。选择一些描述性的东西,让用户更容易理解到底出了什么问题。
接下来,使用PyModule_AddObject将它添加到模块对象中。这将您的模块对象、正在添加的新对象的名称以及自定义异常对象本身作为参数。最后,您返回您的模块对象。
现在,您已经为您的模块定义了一个要引发的定制异常,您需要更新method_fputs()以便它引发适当的异常:
1static PyObject *method_fputs(PyObject *self, PyObject *args) { 2 char *str, *filename = NULL; 3 int bytes_copied = -1; 4
5 /* Parse arguments */ 6 if(!PyArg_ParseTuple(args, "ss", &str, &fd)) { 7 return NULL; 8 } 9
10 if (strlen(str) < 10) { 11 /* Passing custom exception */ 12 PyErr_SetString(StringTooShortError, "String length must be greater than 10"); 13 return NULL; 14 } 15
16 fp = fopen(filename, "w"); 17 bytes_copied = fputs(str, fp); 18 fclose(fp); 19
20 return PyLong_FromLong(bytes_copied); 21}
使用新的更改生成模块后,您可以通过尝试编写长度小于 10 个字符的字符串来测试自定义异常是否按预期工作:
>>> import fputs
>>> # Custom exception
>>> fputs.fputs("RP!", fp.fileno())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
fputs.StringTooShortError: String length must be greater than 10
当您尝试编写少于 10 个字符的字符串时,您的自定义异常会引发一条解释错误原因的消息。
定义常数
有些情况下,您会希望在 Python C 扩展模块中使用或定义常量。这与您在上一节中定义自定义异常的方式非常相似。您可以定义一个新的常量,并使用PyModule_AddIntConstant()将其添加到您的模块实例中:
PyMODINIT_FUNC PyInit_fputs(void) { /* Assign module value */ PyObject *module = PyModule_Create(&fputsmodule); /* Add int constant by name */ PyModule_AddIntConstant(module, "FPUTS_FLAG", 64);
/* Define int macro */ #define FPUTS_MACRO 256
/* Add macro to module */ PyModule_AddIntMacro(module, FPUTS_MACRO); return module; }
此 Python API 函数采用以下参数:
- 你的模块的实例
- 常量的名
- 常数的值
您可以使用PyModule_AddIntMacro()对宏进行同样的操作:
PyMODINIT_FUNC PyInit_fputs(void) { /* Assign module value */ PyObject *module = PyModule_Create(&fputsmodule); /* Add int constant by name */ PyModule_AddIntConstant(module, "FPUTS_FLAG", 64); /* Define int macro */ #define FPUTS_MACRO 256
/* Add macro to module */ PyModule_AddIntMacro(module, FPUTS_MACRO);
return module; }
该函数采用以下参数:
- 你的模块的实例
- 已经定义的宏的名
**注意:**如果你想给你的模块添加字符串常量或者宏,那么你可以分别使用PyModule_AddStringConstant()和PyModule_AddStringMacro()。
打开 Python 解释器,查看您的常量和宏是否按预期工作:
>>> import fputs
>>> # Constants
>>> fputs.FPUTS_FLAG
64
>>> fputs.FPUTS_MACRO
256
在这里,您可以看到这些常量可以从 Python 解释器中访问。
测试您的模块
您可以像测试任何其他 Python 模块一样测试您的 Python C 扩展模块。这可以通过为 pytest 编写一个小的测试函数来演示:
import fputs
def test_copy_data():
content_to_copy = "Real Python!"
bytes_copied = fputs.fputs(content_to_copy, 'test_write.txt')
with open('test_write.txt', 'r') as f:
content_copied = f.read()
assert content_copied == content_to_copy
在上面的测试脚本中,您使用fputs.fputs()将字符串"Real Python!"写入一个名为test_write.txt的空文件。然后,你读入这个文件的内容,并使用一个 assert语句将其与你最初写的内容进行比较。
您可以运行这个测试套件来确保您的模块按预期工作:
$ pytest -q
test_fputs.py [100%]
1 passed in 0.03 seconds
要获得更深入的介绍,请查看Python 测试入门。
考虑替代方案
在本教程中,您已经为 C 库函数构建了一个接口,以理解如何编写 Python C 扩展模块。然而,有时您需要做的只是调用一些系统调用或一些 C 库函数,并且您希望避免编写两种不同语言的开销。在这些情况下,可以使用 ctypes 或者 cffi 等 Python 库。
这些是 Python 的外部函数库,提供对 C 库函数和数据类型的访问。尽管社区本身在哪个图书馆最好的问题上存在分歧,但两者都有各自的优点和缺点。换句话说,对于任何给定的项目来说,这两种方法都是不错的选择,但是当您需要在两者之间做出选择时,需要记住一些事情:
-
Python 标准库中包含了
ctypes库。如果你想避免外部依赖,这是非常重要的。它允许你用 Python 为其他语言编写包装器。 -
cffi库尚未包含在标准库中。这可能会成为你特定项目的绊脚石。总的来说,它本质上更像蟒蛇,但是它不为你处理预处理。
有关这些库的更多信息,请查看用 C 库扩展 Python 和“ctypes”模块和Python 和 C 的接口:CFFI 模块。
**注:**除了ctypes和cffi之外,还有其他各种工具可用。例如,你也可以使用swig和boost::Py。
结论
在本教程中,你已经学习了如何使用 Python API 用 C 编程语言编写一个 Python 接口。您为fputs() C 库函数编写了一个 Python 包装器。在构建和测试模块之前,您还向模块添加了自定义异常和常量。
Python API 为用 C 编程语言编写复杂的 Python 接口提供了许多特性。同时,像cffi或ctypes这样的库可以降低编写 Python C 扩展模块的开销。确保你在做决定之前权衡了所有的因素!******


