在 C 中编写 python 扩展时,如何将 python 函数传递给 C 函数?
When writing a python extension in C, how does one pass a python function in to a C function?
首先,对于令人困惑的标题,我们深表歉意。
我想要实现的目标如下:假设我有一些函数 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)
。但我可以使用一些帮助来实现这一点。
传入的 Python 可调用对象将是一个匹配 Callable 协议的 Python 对象。因此,为了将它传递给您的 C 函数,您必须创建另一个 C 函数作为代理,它与您的函数指针所需的签名相匹配。
举个例子:
static PyObject* foo_cb_callable;
static int py_foo_callback(void) {
PyObject* retval;
int result;
// Call the python function/object saved below
retval = PyObject_CallObject(foo_cb_callable, NULL);
// Convert the returned object to an int if possible
if (retval && PyInt_Check(retval))
result = (int)PyInt_AsLong(retval);
else
result = -1;
Py_XDECREF(retval);
return result;
}
// NOTE: I renamed this to avoid conflicting with your "foo"
// function to be called externally.
static PyObject* py_foo(PyObject* self, PyObject* args) {
unsigned int n;
int result;
// Parse the input tuple
if (!PyArg_ParseTuple(args, "OI", &foo_cb_callable, &n)) {
return NULL;
}
// Ensure the first parameter is a callable (e.g. function)
if (!PyCallable_Check(foo_cb_callable)) {
return NULL;
}
// Call foo with our custom wrapper
result = foo(&py_foo_callback, n);
return Py_BuildValue("i", result);
}
请注意,我在示例中使用了全局回调对象指针,因为您的原始函数指针没有放置自定义用户数据的位置。如果您将通用用户参数添加到 func
回调,则 wrap_foo_callback
可以通过这种方式传递给它,而不是创建一个全局变量。例如:
int foo(int(*func)(void*), void* user_data, int i) {
int n = func(user_data) + i;
return n;
}
// ...
static int py_foo_callback(void* callback) {
PyObject* retval;
int result;
// Call the python function/object saved below
retval = PyObject_CallObject((PyObject*)callback, NULL);
// Convert the returned object to an int if possible
if (retval && PyInt_Check(retval))
result = (int)PyInt_AsLong(retval);
else
result = -1;
Py_XDECREF(retval);
return result;
}
static PyObject* py_foo(PyObject* self, PyObject* args) {
PyObject* callback;
unsigned int n;
int result;
// Parse the input tuple
if (!PyArg_ParseTuple(args, "OI", &callback, &n)) {
return NULL;
}
// Ensure the first parameter is a callable (e.g. function)
if (!PyCallable_Check(callback)) {
return NULL;
}
// Call foo with our custom wrapper
result = foo(&py_foo_callback, callback, n);
return Py_BuildValue("i", result);
}
首先,当你有一个用 C 实现的 Python "extension method",并且该函数接收一个 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
通过指向它调用的函数的数据指针传递,您可以使用 "glue" 过程解决此问题:
/* 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;
}
这里的神奇之处在于 [&]() { ... }
符号,它是用于定义和创建局部 class 实例的语法糖,"captures" 变量 cb
和 rv
以便花括号内的代码(将被编译为一个单独的函数)可以与 foo
正确通信。这是一个名为 "lambdas" 的 C++11 特性,这是一个行话术语,可以追溯到理论 CS 的早期,并被 Lisp 永垂不朽。 Here is a tutorial,但我不确定它有多好,因为我已经从里到外了解了这个概念。
不可能在普通 C 中执行此操作,但也不可能从普通 C 调用 BSPLib::Classic::Init
(因为您根本无法定义 std::function
对象纯 C ... 好吧,无论如何,不是没有对 C++ 标准库和 ABI 进行逆向工程)所以没关系。
首先,对于令人困惑的标题,我们深表歉意。
我想要实现的目标如下:假设我有一些函数 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)
。但我可以使用一些帮助来实现这一点。
传入的 Python 可调用对象将是一个匹配 Callable 协议的 Python 对象。因此,为了将它传递给您的 C 函数,您必须创建另一个 C 函数作为代理,它与您的函数指针所需的签名相匹配。
举个例子:
static PyObject* foo_cb_callable;
static int py_foo_callback(void) {
PyObject* retval;
int result;
// Call the python function/object saved below
retval = PyObject_CallObject(foo_cb_callable, NULL);
// Convert the returned object to an int if possible
if (retval && PyInt_Check(retval))
result = (int)PyInt_AsLong(retval);
else
result = -1;
Py_XDECREF(retval);
return result;
}
// NOTE: I renamed this to avoid conflicting with your "foo"
// function to be called externally.
static PyObject* py_foo(PyObject* self, PyObject* args) {
unsigned int n;
int result;
// Parse the input tuple
if (!PyArg_ParseTuple(args, "OI", &foo_cb_callable, &n)) {
return NULL;
}
// Ensure the first parameter is a callable (e.g. function)
if (!PyCallable_Check(foo_cb_callable)) {
return NULL;
}
// Call foo with our custom wrapper
result = foo(&py_foo_callback, n);
return Py_BuildValue("i", result);
}
请注意,我在示例中使用了全局回调对象指针,因为您的原始函数指针没有放置自定义用户数据的位置。如果您将通用用户参数添加到 func
回调,则 wrap_foo_callback
可以通过这种方式传递给它,而不是创建一个全局变量。例如:
int foo(int(*func)(void*), void* user_data, int i) {
int n = func(user_data) + i;
return n;
}
// ...
static int py_foo_callback(void* callback) {
PyObject* retval;
int result;
// Call the python function/object saved below
retval = PyObject_CallObject((PyObject*)callback, NULL);
// Convert the returned object to an int if possible
if (retval && PyInt_Check(retval))
result = (int)PyInt_AsLong(retval);
else
result = -1;
Py_XDECREF(retval);
return result;
}
static PyObject* py_foo(PyObject* self, PyObject* args) {
PyObject* callback;
unsigned int n;
int result;
// Parse the input tuple
if (!PyArg_ParseTuple(args, "OI", &callback, &n)) {
return NULL;
}
// Ensure the first parameter is a callable (e.g. function)
if (!PyCallable_Check(callback)) {
return NULL;
}
// Call foo with our custom wrapper
result = foo(&py_foo_callback, callback, n);
return Py_BuildValue("i", result);
}
首先,当你有一个用 C 实现的 Python "extension method",并且该函数接收一个 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
通过指向它调用的函数的数据指针传递,您可以使用 "glue" 过程解决此问题:
/* 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;
}
这里的神奇之处在于 [&]() { ... }
符号,它是用于定义和创建局部 class 实例的语法糖,"captures" 变量 cb
和 rv
以便花括号内的代码(将被编译为一个单独的函数)可以与 foo
正确通信。这是一个名为 "lambdas" 的 C++11 特性,这是一个行话术语,可以追溯到理论 CS 的早期,并被 Lisp 永垂不朽。 Here is a tutorial,但我不确定它有多好,因为我已经从里到外了解了这个概念。
不可能在普通 C 中执行此操作,但也不可能从普通 C 调用 BSPLib::Classic::Init
(因为您根本无法定义 std::function
对象纯 C ... 好吧,无论如何,不是没有对 C++ 标准库和 ABI 进行逆向工程)所以没关系。