You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python C API中向回调函数传递额外数据?

如何通过Python C API向回调函数传递额外数据

我正在用Python C API实现C与Python的集成,需要注册携带额外数据的回调函数,希望回调被调用时能访问这些数据。当前回调签名是:

PyObject* callback(PyObject* self, PyObject* args);

想实现类似Tcl库中传递客户端数据的功能,但不知道怎么在Python C API里做到这一点。现有代码如下:

#include <Python.h>

// 定义存储额外数据的结构体
typedef struct {
    int some_value;
} CallbackData;

// 回调函数
PyObject* callback(PyObject* self, PyObject* args) {
    // 这里需要获取并使用额外数据
    // ...
}

// 注册回调的函数
void register_callback(PyObject* module) {
    // 创建额外数据
    CallbackData* data = (CallbackData*)malloc(sizeof(CallbackData));
    data->some_value = 42;
    // 不知道该把数据传到哪里才能让回调访问到?
    PyMethodDef* foo_def = new PyMethodDef{"foo", callback, METH_VARARGS, "Docs"};
    PyObject* foo = PyCFunction_New(foo_def, NULL);
    PyModule_AddObject(module, "foo", foo);
}

解决方案:使用PyCapsule封装客户端数据

Python C API提供的PyCapsule类型可以安全封装C指针,并作为self参数传递给回调函数,同时能被Python垃圾回收机制管理,避免内存泄漏。修改步骤如下:

  1. 封装数据到PyCapsule:注册回调时,把分配好的CallbackData指针封装成PyCapsule对象。
  2. 回调中提取数据:从self参数(即PyCapsule对象)中取出封装的指针,访问其中的数据。
  3. 设置销毁函数:确保Python回收Capsule时,自动释放CallbackData的内存。

修改后的完整代码:

#include <Python.h>

// 定义存储额外数据的结构体
typedef struct {
    int some_value;
} CallbackData;

// Capsule销毁函数:释放CallbackData内存
static void callback_data_destroy(PyObject* capsule) {
    CallbackData* data = (CallbackData*)PyCapsule_GetPointer(capsule, "CallbackData");
    if (data != NULL) {
        free(data);
    }
}

// 回调函数:从self(PyCapsule)中提取数据
PyObject* callback(PyObject* self, PyObject* args) {
    // 从Capsule中取出CallbackData指针
    CallbackData* data = (CallbackData*)PyCapsule_GetPointer(self, "CallbackData");
    if (data == NULL) {
        return NULL; // 提取失败,Python会自动抛出异常
    }

    // 使用额外数据
    printf("Callback accessed some_value: %d\n", data->some_value);

    Py_RETURN_NONE; // 返回Python的None对象
}

// 注册回调的函数
void register_callback(PyObject* module) {
    // 创建额外数据
    CallbackData* data = (CallbackData*)malloc(sizeof(CallbackData));
    data->some_value = 42;

    // 将数据封装为PyCapsule,指定销毁函数和唯一标识字符串
    PyObject* capsule = PyCapsule_New(data, "CallbackData", callback_data_destroy);
    if (capsule == NULL) {
        free(data); // 创建Capsule失败,手动释放数据
        return;
    }

    // 创建PyCFunction时,将capsule作为self参数传入
    PyMethodDef foo_def = {"foo", callback, METH_VARARGS, "Demo callback with extra data"};
    PyObject* foo = PyCFunction_New(&foo_def, capsule);
    if (foo == NULL) {
        Py_DECREF(capsule); // 创建函数失败,释放Capsule
        return;
    }

    // 将函数添加到模块
    if (PyModule_AddObject(module, "foo", foo) != 0) {
        Py_DECREF(foo); // 添加失败,释放函数对象
    }
}

关键细节说明

  • 唯一标识字符串:PyCapsule_New的第三个参数是字符串标识,PyCapsule_GetPointer需要匹配这个标识才能取出指针,避免类型错误。
  • 内存安全:销毁函数callback_data_destroy会在Python回收Capsule时自动调用,释放malloc分配的CallbackData,无需手动管理。
  • 错误处理:每一步都检查API调用返回值,避免内存泄漏和未定义行为。

替代方案:自定义扩展类型

如果需要传递更复杂的结构,或希望数据能被Python代码直接访问,可以自定义Python扩展类型,将CallbackData作为类型成员变量。这种方式更灵活,但实现稍复杂,适合Python与C共享数据的场景。


内容的提问来源于stack exchange,提问作者Սոկրատ Գալստյան

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 03:22:23