55、扩展与嵌入 Python:从 C 语言到 Python 的无缝衔接

扩展与嵌入 Python:从 C 语言到 Python 的无缝衔接

1. 宏的使用示例

以下示例展示了一些宏的使用:

PyObject *py_wrapper(PyObject *self, PyObject *args) {
    ...
    PyArg_ParseTuple(args, ...)
    Py_BEGIN_ALLOW_THREADS
    result = run_long_calculation(args);
    Py_END_ALLOW_THREADS
    ...
    return Py_BuildValue(fmt,result);
}
2. 嵌入 Python 解释器

Python 解释器可以嵌入到 C 应用程序中。嵌入后,Python 解释器作为一个编程库运行,C 程序可以初始化解释器、运行脚本和代码片段、加载库模块以及操作 Python 实现的函数和对象。

2.1 嵌入模板

以下是一个简单的 C 程序,展示了最基本的嵌入方式:

#include <Python.h>
int main(int argc, char **argv) {
    Py_Initialize();
    PyRun_SimpleString("print('Hello World')");
    Py_Finalize();
    return 0;
}

在这个例子中,解释器被初始化,一个简短的脚本作为字符串被执行,然后解释器被关闭。在进一步操作之前,先让这个示例正常工作通常是个好主意。

2.2 编译和链接

在 UNIX 上编译嵌入式解释器时,代码必须包含 "Python.h" 头文件,并链接解释器库,如 libpython2.6.a 。头文件通常位于 /usr/local/include/python2.6 ,库文件通常位于 /usr/local/lib/python2.6/config 。在 Windows 上,需要在 Python 安装目录中找到这些文件。需要注意的是,解释器可能依赖于其他库,在链接时需要包含这些库。这通常与平台相关,并且取决于 Python 在机器上的配置,可能需要进行一些调整。

2.3 基本解释器操作和设置

以下函数用于设置解释器并运行脚本:
- int PyRun_AnyFile(FILE *fp, char *filename) :如果 fp 是交互式设备(如 UNIX 中的 tty),则调用 PyRun_InteractiveLoop() ;否则,调用 PyRun_SimpleFile() filename 是输入流的名称,当解释器报告错误时会显示该名称。如果 filename NULL ,则使用默认字符串 "???" 作为文件名。
- int PyRun_SimpleFile(FILE *fp, char *filename) :与 PyRun_SimpleString() 类似,不同之处在于程序从文件 fp 中读取。
- int PyRun_SimpleString(char *command) :在解释器的 __main__ 模块中执行 command 。成功返回 0,发生异常返回 -1。
- int PyRun_InteractiveOne(FILE *fp, char *filename) :执行单个交互式命令。
- int PyRun_InterativeLoop(FILE *fp, char *filename) :以交互式模式运行解释器。
- void Py_Initialize() :初始化 Python 解释器。在使用 C API 中的其他函数之前,应调用此函数,但 Py_SetProgramName() PyEval_InitThreads() PyEval_ReleaseLock() PyEval_AcquireLock() 除外。
- int Py_IsInitialized() :如果解释器已初始化,则返回 1;否则返回 0。
- void Py_Finalize() :通过销毁自调用 Py_Initialize() 以来创建的所有子解释器和对象来清理解释器。通常,此函数会释放解释器分配的所有内存。但是,循环引用和扩展模块可能会导致内存泄漏,此函数无法回收这些泄漏的内存。
- void Py_SetProgramName(char *name) :设置通常在 sys 模块的 argv[0] 参数中找到的程序名称。此函数应仅在 Py_Initialize() 之前调用。
- char *Py_GetPrefix() :返回已安装的与平台无关的文件的前缀,与 sys.prefix 中的值相同。
- char *Py_GetExecPrefix() :返回已安装的与平台相关的文件的执行前缀,与 sys.exec_prefix 中的值相同。
- char *Py_GetProgramFullPath() :返回 Python 可执行文件的完整路径名。
- char *Py_GetPath() :返回默认的模块搜索路径。路径作为一个字符串返回,由目录名组成,目录名之间用与平台相关的分隔符分隔(UNIX 上为 : ,DOS/Windows 上为 ; )。
- int PySys_SetArgv(int argc, char **argv) :设置用于填充 sys.argv 值的命令行选项。此函数应仅在 Py_Initialize() 之前调用。

3. 从 C 访问 Python

虽然有多种方法可以从 C 访问解释器,但在嵌入时,最常见的四个基本任务是:
- 导入 Python 库模块(模拟 import 语句)
- 获取模块中定义的对象的引用
- 调用 Python 函数、类和方法
- 访问对象的属性(数据、方法等)

所有这些操作都可以使用 Python C API 中定义的基本操作来完成:
- PyObject *PyImport_ImportModule(const char *modname) :导入模块 modname 并返回关联模块对象的引用。
- PyObject *PyObject_GetAttrString(PyObject *obj, const char *name) :从对象中获取属性,与 obj.name 相同。
- int PyObject_SetAttrString(PyObject *obj, const char *name, PyObject *value) :在对象上设置属性,与 obj.name = value 相同。
- PyObject *PyEval_CallObject(PyObject *func, PyObject *args) :使用参数 args 调用 func func 是一个 Python 可调用对象(函数、方法、类等), args 是一个参数元组。
- PyObject *PyEval_CallObjectWithKeywords(PyObject *func, PyObject *args, PyObject *kwargs) :使用位置参数 args 和关键字参数 kwargs 调用 func func 是一个可调用对象, args 是一个元组, kwargs 是一个字典。

以下示例展示了如何从 C 调用并使用 re 模块的各个部分。该程序打印出从标准输入读取的所有包含用户提供的 Python 正则表达式的行:

#include "Python.h"
int main(int argc, char **argv) {
    PyObject *re;
    PyObject *re_compile;
    PyObject *pat;
    PyObject *pat_search;
    PyObject *args;
    char      buffer[256];
    if (argc != 2) {
        fprintf(stderr,"Usage: %s pattern\n",argv[0]);
        exit(1);
    }
    Py_Initialize();
    /* import re */
    re   = PyImport_ImportModule("re");
    /* pat = re.compile(pat,flags) */
    re_compile = PyObject_GetAttrString(re,"compile");
    args = Py_BuildValue("(s)", argv[1]);
    pat = PyEval_CallObject(re_compile, args);
    Py_DECREF(args);
    /* pat_search = pat.search  - bound method*/
    pat_search = PyObject_GetAttrString(pat,"search");
    /* Read lines and perform matches */
    while (fgets(buffer,255,stdin)) {
        PyObject *match;
        args = Py_BuildValue("(s)", buffer);
        /* match = pat.search(buffer) */
        match = PyEval_CallObject(pat_search,args);
        Py_DECREF(args);
        if (match != Py_None) {
            printf("%s",buffer);
        }
        Py_XDECREF(match);
    }
    Py_DECREF(pat);
    Py_DECREF(re_compile);
    Py_DECREF(re);
    Py_Finalize();
    return 0;
}

在任何嵌入代码中,正确管理引用计数至关重要。特别是,需要减少从 C 创建或作为函数求值结果返回给 C 的任何对象的引用计数。

4. 将 Python 对象转换为 C

嵌入式使用解释器的一个主要问题是将 Python 函数或方法调用的结果转换为合适的 C 表示形式。一般来说,需要提前确切知道操作将返回哪种类型的数据。遗憾的是,没有像 PyArg_ParseTuple() 这样的高级便利函数来转换单个对象值。但是,以下列出了一些低级转换函数,只要确切知道正在处理的 Python 对象类型,这些函数就可以将一些基本的 Python 数据类型转换为适当的 C 表示形式:
| Python 到 C 的转换函数 | 说明 |
| — | — |
| long PyInt_AsLong(PyObject *) | 将 Python 对象转换为 long 类型 |
| long PyLong_AsLong(PyObject *) | 将 Python 对象转换为 long 类型 |
| double PyFloat_AsDouble(PyObject *) | 将 Python 对象转换为 double 类型 |
| char *PyString_AsString(PyObject *) (Python 2 only) | 将 Python 对象转换为 char * 类型(仅适用于 Python 2) |
| char *PyBytes_AsString(PyObject *) (Python 3 only) | 将 Python 对象转换为 char * 类型(仅适用于 Python 3) |

对于更复杂的类型,需要参考 C API 文档(http://docs.python.org/c-api)。

5. ctypes 模块

ctypes 模块使 Python 能够访问 DLL 和共享库中定义的 C 函数。虽然需要了解一些底层 C 库的详细信息(名称、调用参数、类型等),但可以使用 ctypes 访问 C 代码,而无需编写 C 扩展包装代码或使用 C 编译器进行编译。 ctypes 是一个功能强大的库模块,具有许多高级功能。这里介绍其基本部分。

5.1 加载共享库

以下类用于加载 C 共享库并返回表示其内容的实例:
- CDLL(name [, mode [, handle [, use_errno [, use_last_error]]]]) :表示标准 C 共享库的类。 name 是库的名称,如 'libc.so.6' 'msvcrt.dll' mode 提供标志,用于确定库的加载方式,并传递给 UNIX 上的底层 dlopen() 函数。可以设置为 RTLD_LOCAL RTLD_GLOBAL RTLD_DEFAULT (默认值)的按位或。在 Windows 上, mode 被忽略。 handle 指定已加载库的句柄(如果可用),默认值为 None use_errno 是一个布尔标志,为加载库中 C errno 变量的处理添加额外的安全层。此层在调用任何外部函数之前保存 errno 的线程本地副本,并在之后恢复该值,默认值为 False use_last_error 是一个布尔标志,启用一对函数 get_last_error() set_last_error() ,可用于操作系统错误代码,这些函数在 Windows 上更常用,默认值为 False
- WinDLL(name [, mode [, handle [, use_errno [, use_last_error]]]]) :与 CDLL() 相同,不同之处在于库中的函数假定遵循 Windows stdcall 调用约定(仅适用于 Windows)。

以下实用函数可用于在系统上定位共享库,并构造适合用作上述类中 name 参数的名称。它定义在 ctypes.util 子模块中:
- find_library(name) :返回与库名称对应的路径名。 name 是没有任何文件后缀的库名称,如 'libc' 'libm' 等。该函数返回的字符串是完整的路径名,如 '/usr/lib/libc.so.6' 。此函数的行为高度依赖于系统,并且取决于共享库的底层配置和环境(例如, LD_LIBRARY_PATH 的设置和其他参数)。如果找不到库,则返回 None

graph TD;
    A[开始] --> B[加载共享库];
    B --> C{选择库类型};
    C -->|CDLL| D[标准 C 共享库];
    C -->|WinDLL| E[Windows stdcall 调用约定库];
    D --> F[使用库函数];
    E --> F;
    F --> G[结束];
5.2 外部函数

CDLL() 类创建的共享库实例作为底层 C 库的代理。要访问库内容,只需使用属性查找( . 运算符)。例如:

>>> import ctypes
>>> libc = ctypes.CDLL("/usr/lib/libc.dylib")
>>> libc.rand()
16807
>>> libc.atoi("12345")
12345
>>>

在这个例子中, libc.rand() libc.atoi() 等操作直接调用加载的 C 库中的函数。

ctypes 假设所有函数接受 int char * 类型的参数,并返回 int 类型的结果。因此,尽管前面的函数调用“有效”,但对其他 C 库函数的调用可能不会按预期工作。例如:

>>> libc.atof("34.5")
-1073746168
>>>

为了解决这个问题,可以通过更改以下属性来设置任何外部函数 func 的类型签名和处理方式:
- func.argtypes :描述 func 输入参数的 ctypes 数据类型元组。
- func.restype :描述 func 返回值的 ctypes 数据类型。对于返回 void 的函数,使用 None
- func.errcheck :一个接受三个参数( result func args )的 Python 可调用对象,其中 result 是外部函数返回的值, func 是对外部函数本身的引用, args 是输入参数的元组。此函数在外部函数调用后调用,可用于执行错误检查和其他操作。

以下是修复 atof() 函数接口的示例:

>>> libc.atof.restype=ctypes.c_double
>>> libc.atof("34.5")
34.5
>>>

ctypes.d_double 是对预定义数据类型的引用。下一节将介绍这些数据类型。

5.3 数据类型

表 1 显示了可用于外部函数的 argtypes restype 属性的 ctypes 数据类型。“Python 值”列描述了给定数据类型接受的 Python 数据类型。

ctypes 类型名称 C 数据类型 Python 值
c_bool bool True False
c_bytes signed char 小整数
c_char char 单个字符
c_char_p char * 以空字符结尾的字符串或字节
c_double double 浮点数
c_longdouble long double 浮点数
c_float float 浮点数
c_int int 整数
c_int8 signed char 8 位整数
c_int16 short 16 位整数
c_int32 int 32 位整数
c_int64 long long 64 位整数
c_long long 整数
c_longlong long long 整数
c_short short 整数
c_size_t size_t 整数
c_ubyte unsigned char 无符号整数
c_uint unsigned int 无符号整数
c_uint8 unsigned char 8 位无符号整数
c_uint16 unsigned short 16 位无符号整数
c_uint32 unsigned int 32 位无符号整数
c_uint64 unsigned long long 64 位无符号整数
c_ulong unsigned long 无符号整数
c_ulonglong unsigned long long 无符号整数
c_ushort unsigned short 无符号整数
c_void_p void * 整数
c_wchar wchar_t 单个 Unicode 字符
c_wchar_p wchar_t * 以空字符结尾的 Unicode 字符串

要创建表示 C 指针的类型,可以对其他类型应用以下函数:
- POINTER(type) :定义一个指向 type 类型的指针类型。例如, POINTER(c_int) 表示 C 类型 int *

要定义表示固定大小 C 数组的类型,可以将现有类型乘以数组维度数。例如, c_int*4 表示 C 数据类型 int[4]

要定义表示 C 结构或联合的类型,可以从基类 Structure Union 继承。在每个派生类中,定义一个类变量 _fields_ 来描述内容。 _fields_ 是一个由 2 或 3 个元组组成的列表,形式为 (name, ctype) (name, ctype, width) ,其中 name 是结构字段的标识符, ctype 是描述类型的 ctype 类, width 是整数位字段宽度。例如,考虑以下 C 结构:

struct Point {
    double x, y;
};

该结构的 ctypes 描述如下:

class Point(Structure):
    _fields_ = [ ("x", c_double),
                 ("y", c_double) ]
5.4 调用外部函数

要调用库中的函数,只需使用与函数类型签名兼容的一组参数调用相应的函数。对于简单的数据类型,如 c_int c_double 等,可以直接传递兼容的 Python 类型作为输入(整数、浮点数等)。也可以传递 c_int c_double 等类型的实例作为输入。对于数组,可以直接传递兼容类型的 Python 序列。

要将指针传递给外部函数,必须首先创建一个表示将被指向的值的 ctypes 实例,然后使用以下函数之一创建指针对象:
- byref(cvalue [, offset]) :表示指向 cvalue 的轻量级指针。 cvalue 必须是 ctypes 数据类型的实例。 offset 是要添加到指针值的字节偏移量。该函数返回的值只能在函数调用中使用。
- pointer(cvalue) :创建一个指向 cvalue 的指针实例。 cvalue 必须是 ctypes 数据类型的实例。这将创建前面描述的 POINTER 类型的实例。

以下示例展示了如何将 double * 类型的参数传递给 C 函数:

dval = c_double(0.0)          # Create a double instance
r = foo(byref(dval))          # Calls foo(&dval)
p_dval = pointer(dval)        # Creates a pointer variable
r = foo(p_dval)               # Calls foo(p_dval)
# Inspect the value of dval afterwards
print (dval.value)

需要注意的是,不能创建指向内置类型(如 int float )的指针。如果底层 C 函数更改了值,传递指向此类类型的指针将违反可变性。

ctypes 实例 cobj cobj.value 属性包含内部数据。例如,前面代码中对 dval.value 的引用返回存储在 ctypes c_double 实例 dval 中的浮点值。

要将结构传递给 C 函数,必须创建结构或联合的实例。为此,可以按如下方式调用先前定义的结构或联合类型 StructureType

StructureType(*args, **kwargs)

创建 StructureType 的实例,其中 StructureType 是从 Structure Union 派生的类。 *args 中的位置参数用于按 _fields_ 中列出的顺序初始化结构成员。 **kwargs 中的关键字参数仅初始化命名的结构成员。

5.5 替代类型构造方法

所有 ctypes 类型(如 c_int POINTER 等)的实例都有一些类方法,用于从内存位置和其他对象创建 ctypes 类型的实例。
- ty.from_buffer(source [,offset]) :创建一个与 source 共享相同内存缓冲区的 ctypes 类型 ty 的实例。 source 必须是支持可写缓冲区接口的任何其他对象(例如, bytearray array 模块中的数组对象、 mmap 等)。 offset 是从缓冲区开始使用的字节数。
- ty.from_buffer_copy(source [, offset]) :与 ty.from_buffer() 相同,不同之处在于会复制内存,并且 source 可以是只读的。
- ty.from_address(address) :从指定为整数的原始内存地址 address 创建 ctypes 类型 ty 的实例。
- ty.from_param(obj) :从 Python 对象 obj 创建 ctypes 类型 ty 的实例。只有当传递的对象 obj 可以适应适当的类型时,此方法才有效。例如,Python 整数可以适应 c_int 实例。
- ty.in_dll(library, name) :从共享库中的符号创建 ctypes 类型 ty 的实例。 library 是加载的库的实例,如 CDLL 类创建的对象。 name 是符号的名称。此方法可用于在库中定义的全局变量周围放置 ctypes 包装器。

以下示例展示了如何创建对库 libexample.so 中定义的全局变量 int status 的引用:

libexample = ctypes.CDLL("libexample.so")
status = ctypes.c_int.in_dll(libexample,"status")
5.6 实用函数

ctypes 定义了以下实用函数:
- addressof(cobj) :返回 cobj 的内存地址作为整数。 cobj 必须是 ctypes 类型的实例。
- alignment(ctype_or_obj) :返回 ctypes 类型或对象的整数对齐要求。 ctype_or_obj 必须是 ctypes 类型或类型的实例。
- cast(cobj, ctype) :将 ctypes 对象 cobj 转换为 ctype 给定的新类型。此方法仅适用于指针,因此 cobj 必须是指针或数组, ctype 必须是指针类型。
- create_string_buffer(init [, size]) :创建一个可变字符缓冲区,作为 c_char 类型的 ctypes 数组。 init 可以是整数大小或表示初始内容的字符串。 size 是可选参数,如果 init 是字符串,则指定要使用的大小。默认情况下,大小设置为比 init 中的字符数大 1。Unicode 字符串使用默认编码编码为字节。
- create_unicode_buffer(init [, size]) :与 create_string_buffer() 相同,不同之处在于创建的是 c_wchar 类型的 ctypes 数组。
- get_errno() :返回 ctypes 私有副本的 errno 的当前值。
- get_last_error() :返回 Windows 上 ctypes 私有副本的 LastError 的当前值。
- memmove(dst, src, count) :将 count 字节从 src 复制到 dst src dst 可以是表示内存地址的整数,也可以是可以转换为指针的 ctypes 类型的实例。与 C memmove() 库函数相同。
- memset(dst, c, count) :将从 dst 开始的 count 字节内存设置为字节值 c dst 可以是整数或 ctypes 实例。 c 是表示 0 - 255 范围内字节的整数。
- resize(cobj, size) :调整用于表示 ctypes 对象 cobj 的内部内存大小。 size 是新的字节大小。
- set_conversion_mode(encoding, errors) :设置从 Unicode 字符串转换为 8 位字符串时使用的 Unicode 编码。 encoding 是编码名称,如 'utf-8' errors 是错误处理策略,如 'strict' 'ignore' 。返回包含先前设置的元组 (encoding, errors)
- set_errno(value) :设置 ctypes 私有副本的系统 errno 变量。返回先前的值。
- set_last_error(value) :设置 Windows LastError 变量并返回先前的值。
- sizeof(type_or_cobj) :返回 ctypes 类型或对象的大小(以字节为单位)。
- string_at(address [, size]) :返回表示从地址 address 开始的 size 字节内存的字节字符串。如果省略 size ,则假定字节字符串以空字符结尾。
- wstring_at(address [, size]) :返回表示从地址 address 开始的 size 个宽字符的 Unicode 字符串。如果省略 size ,则假定字符字符串以空字符结尾。

5.7 示例

以下示例展示了如何使用 ctypes 模块构建一个接口,以访问本章第一部分中手动创建 Python 扩展模块时使用的一组 C 函数:

# example.py
import ctypes
_example = ctypes.CDLL("./libexample.so")
# int gcd(int, int) 
gcd = _example.gcd
gcd.argtypes = (ctypes.c_int, 
                ctypes.c_int)
gcd.restype = ctypes.c_int
# int replace(char *s, char olcdh, char newch)
_example.replace.argtypes = (ctypes.c_char_p,
                             ctypes.c_char,
                             ctypes.c_char)
_example.replace.restype = ctypes.c_int
def replace(s, oldch, newch):
    sbuffer = ctypes.create_string_buffer(s)
    nrep = _example.replace(sbuffer,oldch,newch)
    return (nrep,sbuffer.value)
# double distance(Point *p1, Point *p2) 
class Point(ctypes.Structure):
    _fields_ = [ ("x", ctypes.c_double),
                 ("y", ctypes.c_double) ]
_example.distance.argtypes = (ctypes.POINTER(Point), 
                              ctypes.POINTER(Point))
_example.distance.restype = ctypes.c_double
def distance(a,b):
    p1 = Point(*a)
    p2 = Point(*b)
    return _example.distance(byref(p1),byref(p2))

通过以上内容,我们详细介绍了在 C 中嵌入 Python 解释器以及使用 ctypes 模块访问 C 代码的方法,包括宏的使用、基本操作函数、数据类型转换、外部函数调用等方面。这些技术可以帮助开发者在不同的场景中灵活运用 Python 和 C 的优势,实现更高效的编程。

扩展与嵌入 Python:从 C 语言到 Python 的无缝衔接

6. 综合应用与注意事项

在实际应用中,将 Python 嵌入 C 或者使用 ctypes 模块访问 C 代码,需要综合考虑多个方面,以确保程序的正确性和性能。

6.1 内存管理

在嵌入 Python 解释器的 C 代码中,正确的内存管理至关重要。如前面提到的,需要注意引用计数的管理。当从 Python 获得对象引用时,引用计数会增加;而当不再使用这些对象时,需要调用 Py_DECREF Py_XDECREF 来减少引用计数,防止内存泄漏。例如在前面的 re 模块调用示例中:

Py_DECREF(args);
Py_DECREF(pat);
Py_DECREF(re_compile);
Py_DECREF(re);

这些操作确保了不再使用的对象所占用的内存能够被正确释放。

在使用 ctypes 时,虽然 Python 会自动管理大部分内存,但对于手动分配的内存,如使用 create_string_buffer 创建的缓冲区,需要确保在不再使用时进行适当的处理。

6.2 错误处理

无论是嵌入 Python 解释器还是使用 ctypes 调用 C 函数,都需要进行错误处理。在 Python 嵌入中, PyRun_SimpleString 等函数会返回错误码,可以根据返回值判断操作是否成功。例如:

if (PyRun_SimpleString("print('Hello World')") == -1) {
    // 处理错误
}

ctypes 中,可以通过设置 func.errcheck 属性来进行错误检查。例如:

def errcheck(result, func, args):
    if result == -1:
        raise OSError("Function call failed")
    return result

libc.some_function.errcheck = errcheck
6.3 性能考虑

在性能方面,嵌入 Python 解释器会带来一定的开销,因为需要初始化和管理解释器。如果需要频繁调用 Python 代码,这种开销可能会影响性能。可以考虑将一些常用的 Python 代码提前编译或缓存,以减少重复执行的开销。

使用 ctypes 调用 C 函数时,参数传递和数据类型转换也会带来一定的性能损失。尽量使用简单的数据类型和直接的参数传递方式,避免不必要的转换。

7. 常见问题及解决方案

在实际使用过程中,可能会遇到一些常见问题,以下是一些解决方案:

7.1 找不到共享库

当使用 CDLL WinDLL 加载共享库时,可能会遇到找不到库的问题。可以使用 find_library 函数来定位库的路径。例如:

import ctypes
from ctypes.util import find_library

lib_path = find_library('libc')
if lib_path:
    libc = ctypes.CDLL(lib_path)
else:
    print("Library not found")
7.2 函数调用结果异常

如前面提到的, ctypes 默认假设函数接受 int char * 类型的参数,并返回 int 类型的结果。如果调用结果异常,需要检查函数的类型签名,通过设置 argtypes restype 属性来确保参数和返回值的类型正确。

7.3 内存泄漏

内存泄漏是一个常见的问题,特别是在嵌入 Python 解释器时。需要仔细检查引用计数的管理,确保所有创建的对象都被正确释放。可以使用内存分析工具来帮助检测和定位内存泄漏问题。

8. 总结

通过本文的介绍,我们了解了如何在 C 中嵌入 Python 解释器以及使用 ctypes 模块访问 C 代码。以下是对主要内容的总结:

  • 嵌入 Python 解释器 :可以通过包含 Python.h 头文件,使用一系列函数如 Py_Initialize PyRun_SimpleString 等来初始化解释器、运行脚本,并在结束时使用 Py_Finalize 清理解释器。在编译和链接时,需要包含相应的头文件和库文件,并注意平台差异。
  • 从 C 访问 Python :可以使用 Python C API 中的函数,如 PyImport_ImportModule PyObject_GetAttrString 等,来导入模块、获取对象属性、调用函数等。同时要注意引用计数的管理。
  • Python 对象转换为 C :可以使用一些低级转换函数,如 PyInt_AsLong PyFloat_AsDouble 等,将基本的 Python 数据类型转换为 C 表示形式。
  • ctypes 模块 :可以使用 CDLL WinDLL 加载共享库,通过设置函数的 argtypes restype 属性来确保正确的参数和返回值类型。同时, ctypes 提供了丰富的数据类型和实用函数,方便与 C 代码进行交互。

通过合理运用这些技术,可以充分发挥 Python 和 C 的优势,实现高效、灵活的编程。在实际应用中,需要根据具体需求和场景,综合考虑内存管理、错误处理和性能等方面的因素,以确保程序的正确性和稳定性。

流程图总结

graph LR;
    A[开始] --> B{选择方式};
    B -->|嵌入 Python 解释器| C[初始化解释器];
    B -->|使用 ctypes| D[加载共享库];
    C --> E[运行 Python 脚本];
    E --> F[清理解释器];
    D --> G[设置函数类型签名];
    G --> H[调用 C 函数];
    F --> I[结束];
    H --> I;

操作步骤总结

嵌入 Python 解释器操作步骤
  1. 包含 Python.h 头文件。
  2. 调用 Py_Initialize 初始化解释器。
  3. 使用 PyRun_SimpleString 或其他相关函数运行脚本。
  4. 处理脚本执行结果,进行必要的错误处理。
  5. 调用 Py_Finalize 清理解释器。
使用 ctypes 访问 C 代码操作步骤
  1. 导入 ctypes 模块。
  2. 使用 CDLL WinDLL 加载共享库。
  3. 设置函数的 argtypes restype 属性。
  4. 调用 C 函数,处理返回结果。
  5. 进行必要的错误处理和内存管理。

通过以上总结和操作步骤,希望能够帮助读者更好地理解和应用这些技术,在实际项目中实现 Python 和 C 的无缝衔接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值