gpt4 book ai didi

python - 在用 C 编写 python 扩展时,如何将 python 函数传递给 C 函数?

转载 作者:太空狗 更新时间:2023-10-29 15:37:25 25 4
gpt4 key购买 nike

首先,为令人困惑的标题道歉。

我想要实现的是以下内容:假设我有一些函数 foo,它接受一个函数和一个整数作为输入。例如

int foo(int(*func)(), int i) {
int n = func() + i;
return n;
}

现在,我想将此函数包装在 python 扩展模块中。所以我开始编写我的界面:

#include <Python.h>

extern "C" {
static PyObject* foo(PyObject* self, PyObject* args);
}

static PyMethodDef myMethods[] = {
{"foo", foo, METH_VARARGS, "Runs foo"},
{NULL, NULL, 0, NULL}
}

// Define the module
static struct PyModuleDef myModule = {
PyModuleDef_HEAD_INIT,
"myModule",
"A Module",
-1,
myMethods
};

// Initialize the module
PyMODINIT_FUNC PyInit_BSPy(void) {
return PyModule_Create(&myModule);
}

//Include the function
static PyObject* foo(PyObject* self, PyObject* args){
// Declare variable/function pointer
int(*bar)(void);
unsigned int n;

// Parse the input tuple
if (!PyArg_ParseTuple(args, ..., &bar, &n)) {
return NULL;
}
}

现在,到了解析输入元组的时候,我感到很困惑,因为我不确定如何解析它。这个想法很简单:我需要能够在 python 中调用 foo(bar(), n)。但我需要一些帮助来了解如何实现这一点。

最佳答案

首先,当您有一个用 C 实现的 Python“扩展方法”,并且该函数接收一个 Python 可调用对象作为参数时,下面是您如何接收参数以及如何调用可调用对象:

/* this code uses only C features */
static PyObject *
foo(PyObject *self, PyObject *args)
{
PyObject *cb;

// Receive a single argument which can be any Python object
// note: the object's reference count is NOT increased (but it's pinned by
// the argument tuple).
if (!PyArg_ParseTuple(args, "O", &cb)) {
return 0;
}
// determine whether the object is in fact callable
if (!PyCallable_Check(cb)) {
PyErr_SetString(PyExc_TypeError, "foo: a callable is required");
return 0;
}
// call it (no arguments supplied)
// there are a whole bunch of other PyObject_Call* functions for when you want
// to supply arguments
PyObject *rv = PyObject_CallObject(cb, 0);
// if calling it returned 0, must return 0 to propagate the exception
if (!rv) return 0;
// otherwise, discard the object returned and return None
Py_CLEAR(rv);
Py_RETURN_NONE;
}

使用这样的逻辑来包装 bsp_init 的问题是指向 Python 可调用对象的指针是一个 data 指针。如果您将该指针直接传递给 bsp_init , bsp_init会尝试将数据调用为机器代码,它会崩溃。如果bsp_init通过指向它调用的函数的数据指针传递,您可以使用“胶水”过程解决这个问题:

/* this code also uses only C features */
struct bsp_init_glue_args {
PyObject *cb;
PyObject *rv;
};
static void
bsp_init_glue(void *data)
{
struct bsp_init_glue_args *args = data;
args->rv = PyObject_CallObject(args->cb, 0);
}

static PyObject *
foo(PyObject *self, PyObject *args)
{
bsp_init_glue_args ba;
if (!PyArg_ParseTuple(args, "O", &ba.cb)) {
return 0;
}
if (!PyCallable_Check(ba.cb)) {
PyErr_SetString(PyExc_TypeError, "foo: a callable is required");
return 0;
}
bsp_init(bsp_init_glue, (void *)&ba, ...);
if (ba->rv == 0) return 0;
Py_CLEAR(ba->rv);
Py_RETURN_NONE;
}

不幸的是,bsp_init没有这个签名,所以你不能这样做。但是替代界面BSPLib::Classic::Init需要 std::function<void()> ,它是上述模式的面向对象包装器,因此您可以改为这样做:

/* this code requires C++11 */
static PyObject *
foo(PyObject *self, PyObject *args)
{
PyObject *cb;
PyObject *rv = 0;
if (!PyArg_ParseTuple(args, "O", &cb)) {
return 0;
}
if (!PyCallable_Check(cb)) {
PyErr_SetString(PyExc_TypeError, "foo: a callable is required");
return 0;
}

std::function<void()> closure = [&]() {
rv = PyObject_CallObject(cb, 0);
};
BSPLib::Classic::Init(closure, ...);

if (rv == 0) return 0;
Py_CLEAR(rv);
Py_RETURN_NONE;
}

这里的魔法全在 [&]() { ... } 中表示法,它是用于定义和创建“捕获”变量的局部类实例的语法糖 cbrv这样将被编译为单独函数的花括号内的代码可以与 foo 通信恰当的。这是一个称为“lambdas”的 C++11 特性,这是一个行话术语,可以追溯到理论 CS 的早期,并被 Lisp 永垂不朽。 Here is a tutorial ,但我不确定它有多好,因为我已经彻底了解了这个概念。

在纯 C 中不可能做到这一点,但也不可能调用 BSPLib::Classic::Init来自纯 C(因为您根本无法在纯 C 中定义 std::function 对象……好吧,无论如何,不​​是没有对 C++ 标准库和 ABI 进行逆向工程)所以没关系。

关于python - 在用 C 编写 python 扩展时,如何将 python 函数传递给 C 函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53215786/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com