首页 > 解决方案 > 在 C 中编写 python 扩展时,如何将 python 函数传递给 C 函数?

问题描述

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

我想要实现的是以下内容:假设我有一些函数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;
    }
}

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

标签: pythoncpython-c-api

解决方案


首先,当您有一个用 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 可调用对象的指针是数据指针。如果您将该指针直接传递给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需要 a 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;
}

这里的魔力在于[&]() { ... }符号,它是用于定义和创建“捕获”变量的本地类实例的语法糖,cb因此rv花括号内的代码将被编译为单独的函数,可以进行通信与foo适当的。这是一个称为“lambdas”的 C++11 功能,这是一个行话术语,可以追溯到理论 CS 的早期,并被 Lisp 永垂不朽。 这是一个教程,但我不确定它有多好,因为我已经了解了这个概念。

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


推荐阅读