[英]How to pass a python list to a C extension function and append some values?
[英]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);
}
首先,當你有一個Python“擴展方法”,用C實現,並且該函數接收Python可調用作為參數時,接下來是你如何接收參數,以及如何調用callable:
/* 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
采用了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中不可能這樣做,但是也不可能從普通的C調用BSPLib::Classic::Init
(因為你無法在普通的C中完全定義一個std::function
對象。好吧,不是沒有逆向工程C ++標准庫和ABI,無論如何)所以沒關系。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.