Cómo pasar una matriz de C a un script Python incrustado

Estoy corriendo a algunos problemas y me gustaría un poco de ayuda. Tengo un código de pieza, que se utiliza para incrustar un script de python. Esta secuencia de comandos de Python contiene una función que esperará recibir una matriz como argumento (en este caso, estoy usando una matriz de números en la secuencia de comandos de Python). Me gustaría saber cómo puedo pasar una matriz de C al script Python incrustado como un argumento para la función dentro del script. Más específicamente puede alguien mostrarme un ejemplo simple de esto.

Realmente, la mejor respuesta aquí es, probablemente, usar matrices numpy exclusivamente, incluso desde su código C. Pero si eso no es posible, entonces tiene el mismo problema que cualquier código que comparte datos entre los tipos C y los tipos Python.

En general, hay al menos cinco opciones para compartir datos entre C y Python:

  1. Crea una list Python u otro objeto para pasar.
  2. Defina un nuevo tipo de Python (en su código C) para envolver y representar la matriz, con los mismos métodos que definiría para un objeto de secuencia en Python ( __getitem__ , etc.).
  3. intptr_t el puntero a la matriz a intptr_t , o al tipo de ctypes explícito, o simplemente déjelo sin lanzar; luego usa ctypes en el lado de Python para acceder a él.
  4. Ponga el puntero a la matriz para const char * y páselo como un str (o, en Py3, bytes ), y use struct o ctypes en el lado de Python para acceder a él.
  5. Cree un objeto que coincida con el protocolo del buffer y, de nuevo, use struct o ctypes en el lado de Python.

En su caso, desea usar numpy.array s en Python. Así, los casos generales se convierten en:

  1. Crea un numpy.array para pasar.
  2. (probablemente no sea apropiado)
  3. Pase el puntero a la matriz como está, y desde Python, use ctypes para obtener un tipo que numpy pueda convertir en una matriz.
  4. Ponga el puntero a la matriz para const char * y páselo como un str (o, en Py3, bytes ), que ya es un tipo que numpy puede convertir en una matriz.
  5. Cree un objeto que coincida con el protocolo del buffer y que, de nuevo, creo que numpy puede convertir directamente.

Para 1, aquí está cómo hacerlo con una list , solo porque es un ejemplo muy simple (y ya lo escribí …):

 PyObject *makelist(int array[], size_t size) { PyObject *l = PyList_New(size); for (size_t i = 0; i != size; ++i) { PyList_SET_ITEM(l, i, PyInt_FromLong(array[i])); } return l; } 

Y aquí está el equivalente numpy.array (asumiendo que puede confiar en que la array C no se eliminará; vea Crear arrays en los documentos para obtener más detalles sobre sus opciones aquí):

 PyObject *makearray(int array[], size_t size) { npy_int dim = size; return PyArray_SimpleNewFromData(1, &dim, (void *)array); } 

En cualquier caso, sin embargo, al hacer esto, terminará con algo que parece un PyObject * de C (y tiene un solo refcount), por lo que puede pasarlo como un argumento de función, mientras que en el lado de Python se verá como un numpy.array , list , bytes , o cualquier otra cosa que sea apropiada.

Ahora, ¿cómo pasas los argumentos de la función? Bueno, el código de muestra en Pure Embedding al que hizo referencia en su comentario muestra cómo hacerlo, pero en realidad no explica qué está sucediendo. En realidad, hay más explicaciones en los documentos extendidos que en los documentos incrustados, específicamente, Llamar a funciones de Python desde C. Además, tenga en cuenta que el código fuente de la biblioteca estándar está repleto de ejemplos de esto (aunque algunos de ellos no son tan legibles como podrían ser, ya sea debido a la optimización, o simplemente porque no se han actualizado para aprovecharlos). de nuevas características de la API C simplificada).

Omita el primer ejemplo sobre cómo obtener una función de Python de Python, porque probablemente ya tenga eso. El segundo ejemplo (y el párrafo a la derecha) muestra la manera fácil de hacerlo: crear una tupla de argumento con Py_BuildValue . Entonces, digamos que queremos llamar a una función que tienes almacenada en myfunc con la lista mylist devuelta por esa función de lista de mylist arriba. Esto es lo que haces:

 if (!PyCallable_Check(myfunc)) { PyErr_SetString(PyExc_TypeError, "function is not callable?!"); return NULL; } PyObject *arglist = Py_BuildValue("(o)", mylist); PyObject *result = PyObject_CallObject(myfunc, arglist); Py_DECREF(arglist); return result; 

Puede omitir la verificación de llamada si está seguro de que tiene un objeto de llamada válido, por supuesto. (Y, por lo general, es mejor verificar cuándo recibe myfunc primera vez, si corresponde, ya que puede proporcionar comentarios de error tanto antes como mejor).

Si realmente quieres entender lo que está sucediendo, pruébalo sin Py_BuildValue . Como dicen los documentos, el segundo argumento de [PyObject_CallObject][6] es una tupla, y PyObject_CallObject(callable_object, args) es equivalente a apply(callable_object, args) , que es equivalente a callable_object(*args) . Entonces, si quisieras llamar a myfunc(mylist) en Python, tienes que convertir eso en, efectivamente, myfunc(*(mylist,)) para que puedas traducirlo a C. Puedes construir una tuple como esta:

 PyObject *arglist = PyTuple_Pack(1, mylist); 

Pero, por lo general, Py_BuildValue es más fácil (especialmente si aún no ha empaquetado todo como objetos Python), y la intención en su código es más clara (al igual que usar PyArg_ParseTuple es más simple y más claro que usar funciones explícitas de tuple en la otra dirección).

Entonces, ¿cómo se consigue que myfunc ? Bueno, si ha creado la función a partir del código de incrustación, simplemente mantenga el puntero alrededor. Si desea que se transmita desde el código Python, eso es exactamente lo que hace el primer ejemplo. Si desea, por ejemplo, buscarlo por nombre desde un módulo u otro contexto, las API para tipos concretos como PyModule y tipos abstractos como PyMapping son bastante simples, y generalmente es obvio cómo convertir el código Python en el código C equivalente, Incluso si el resultado es mayormente feo repetitivo.

Poniéndolo todo junto, digamos que tengo una matriz C de enteros, y quiero import mymodule y llamar a una función mymodule.myfunc(mylist) que devuelve un int. Aquí hay un ejemplo simplificado (no probado en realidad, y sin manejo de errores, pero debería mostrar todas las partes):

 int callModuleFunc(int array[], size_t size) { PyObject *mymodule = PyImport_ImportModule("mymodule"); PyObject *myfunc = PyObject_GetAttrString(mymodule, "myfunc"); PyObject *mylist = PyList_New(size); for (size_t i = 0; i != size; ++i) { PyList_SET_ITEM(l, i, PyInt_FromLong(array[i])); } PyObject *arglist = Py_BuildValue("(o)", mylist); PyObject *result = PyObject_CallObject(myfunc, arglist); int retval = (int)PyInt_AsLong(result); Py_DECREF(result); Py_DECREF(arglist); Py_DECREF(mylist); Py_DECREF(myfunc); Py_DECREF(mymodule); return retval; } 

Si está utilizando C ++, probablemente quiera ver algún tipo de scope-guard / janitor / etc. para manejar todas esas llamadas Py_DECREF , especialmente una vez que comience a manejar adecuadamente los errores (lo que generalmente significa que las llamadas return NULL anticipada salpican la función). Si está utilizando C ++ 11 o Boost, unique_ptr puede ser todo lo que necesita.

Pero en realidad, si planeas hacer mucha comunicación Python en C <->, una mejor manera de reducir todo lo feo es mirar todos los marcos familiares diseñados para mejorar Python: Cython , boost :: python , etc. Aunque esté incrustando, efectivamente está haciendo el mismo trabajo que extendiendo, por lo que pueden ayudar de la misma manera.

Para el caso, algunos de ellos también tienen herramientas para ayudar a la parte de incrustación, si busca en los documentos. Por ejemplo, puede escribir su progtwig principal en Cython, utilizando tanto el código C como el código Python, y cython --embed . Es posible que desee cruzar los dedos y / o sacrificar algunas gallinas, pero si funciona, es increíblemente simple y productivo. Boost no es tan trivial como para comenzar, pero una vez que tienes las cosas juntas, casi todo se hace exactamente de la manera que esperas, y simplemente funciona, y eso es tan cierto para incrustar como extender. Y así.

La función de Python necesitará que se pase un objeto de Python. Ya que desea que ese objeto de Python sea una matriz NumPy, debe usar una de las funciones NumPy C-API para crear matrices ; PyArray_SimpleNewFromData() es probablemente un buen comienzo. Utilizará el buffer provisto, sin copiar los datos.

Dicho esto, casi siempre es más fácil escribir el progtwig principal en Python y usar un módulo de extensión C para el código C. Este enfoque hace que sea más fácil permitir que Python haga la administración de la memoria, y el módulo ctypes junto con las extensiones de cpython de cpython facilitan la transferencia de una matriz NumPy a una función C.