Python - Programación de extensiones con C

Cualquier código que escriba con cualquier lenguaje compilado como C, C ++ o Java puede integrarse o importarse en otro script de Python. Este código se considera una "extensión".

Un módulo de extensión de Python no es más que una biblioteca C normal. En máquinas Unix, estas bibliotecas suelen terminar en.so(para objeto compartido). En las máquinas con Windows, normalmente verá.dll (para biblioteca vinculada dinámicamente).

Requisitos previos para las extensiones de escritura

Para comenzar a escribir su extensión, necesitará los archivos de encabezado de Python.

  • En máquinas Unix, esto generalmente requiere la instalación de un paquete específico para el desarrollador, como python2.5-dev .

  • Los usuarios de Windows obtienen estos encabezados como parte del paquete cuando usan el instalador binario de Python.

Además, se asume que tiene un buen conocimiento de C o C ++ para escribir cualquier Extensión de Python usando la programación C.

Primer vistazo a una extensión de Python

Para ver por primera vez un módulo de extensión de Python, debe agrupar su código en cuatro partes:

  • El archivo de encabezado Python.h .

  • Las funciones de C que desea exponer como la interfaz de su módulo.

  • Una tabla que mapea los nombres de sus funciones como las ven los desarrolladores de Python con las funciones de C dentro del módulo de extensión.

  • Una función de inicialización.

El archivo de encabezado Python.h

Necesita incluir el archivo de encabezado Python.h en su archivo fuente C, que le da acceso a la API interna de Python que se usa para conectar su módulo al intérprete.

Asegúrese de incluir Python.h antes de cualquier otro encabezado que pueda necesitar. Debe seguir las inclusiones con las funciones que desea llamar desde Python.

Las funciones C

Las firmas de la implementación C de sus funciones siempre toman una de las siguientes tres formas:

static PyObject *MyFunction( PyObject *self, PyObject *args );

static PyObject *MyFunctionWithKeywords(PyObject *self,
                                 PyObject *args,
                                 PyObject *kw);

static PyObject *MyFunctionWithNoArgs( PyObject *self );

Cada una de las declaraciones anteriores devuelve un objeto Python. No existe una función void en Python como en C. Si no desea que sus funciones devuelvan un valor, devuelva el equivalente en C de PythonNonevalor. Los encabezados de Python definen una macro, Py_RETURN_NONE, que hace esto por nosotros.

Los nombres de sus funciones C pueden ser los que desee, ya que nunca se ven fuera del módulo de extensión. Se definen como función estática .

Sus funciones C generalmente se nombran combinando el módulo Python y los nombres de las funciones, como se muestra aquí:

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   Py_RETURN_NONE;
}

Esta es una función de Python llamada func dentro del módulo del módulo . Pondrá punteros a sus funciones C en la tabla de métodos para el módulo que generalmente viene a continuación en su código fuente.

La tabla de asignación de métodos

Esta tabla de métodos es una matriz simple de estructuras PyMethodDef. Esa estructura se parece a esto:

struct PyMethodDef {
   char *ml_name;
   PyCFunction ml_meth;
   int ml_flags;
   char *ml_doc;
};

Aquí está la descripción de los miembros de esta estructura:

  • ml_name - Este es el nombre de la función que presenta el intérprete de Python cuando se usa en programas de Python.

  • ml_meth - Debe ser la dirección de una función que tenga alguna de las firmas descritas en la sección anterior.

  • ml_flags - Esto le dice al intérprete cuál de las tres firmas está usando ml_meth.

    • Esta bandera generalmente tiene un valor de METH_VARARGS.

    • Esta bandera puede ser OR 'bit a bit con METH_KEYWORDS si desea permitir argumentos de palabras clave en su función.

    • Esto también puede tener un valor de METH_NOARGS que indica que no desea aceptar ningún argumento.

  • ml_doc - Esta es la cadena de documentación de la función, que podría ser NULL si no tiene ganas de escribir una.

Esta tabla debe terminarse con un centinela que consta de valores NULL y 0 para los miembros correspondientes.

Ejemplo

Para la función definida anteriormente, tenemos la siguiente tabla de mapeo de métodos:

static PyMethodDef module_methods[] = {
   { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
   { NULL, NULL, 0, NULL }
};

La función de inicialización

La última parte de su módulo de extensión es la función de inicialización. Esta función es llamada por el intérprete de Python cuando se carga el módulo. Se requiere que la función se llameinitModule, donde Módulo es el nombre del módulo.

La función de inicialización debe exportarse desde la biblioteca que creará. Los encabezados de Python definen PyMODINIT_FUNC para incluir los encantamientos apropiados para que eso suceda para el entorno particular en el que estamos compilando. Todo lo que tiene que hacer es usarlo al definir la función.

Su función de inicialización de C generalmente tiene la siguiente estructura general:

PyMODINIT_FUNC initModule() {
   Py_InitModule3(func, module_methods, "docstring...");
}

Aquí está la descripción de la función Py_InitModule3 :

  • func - Ésta es la función que se exportará.

  • module_methods - Este es el nombre de la tabla de mapeo definido anteriormente.

  • docstring - Este es el comentario que quieres dar en tu extensión.

Poner todo esto junto se ve como lo siguiente:

#include <Python.h>

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   Py_RETURN_NONE;
}

static PyMethodDef module_methods[] = {
   { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
   { NULL, NULL, 0, NULL }
};

PyMODINIT_FUNC initModule() {
   Py_InitModule3(func, module_methods, "docstring...");
}

Ejemplo

Un ejemplo simple que hace uso de todos los conceptos anteriores:

#include <Python.h>

static PyObject* helloworld(PyObject* self) {
   return Py_BuildValue("s", "Hello, Python extensions!!");
}

static char helloworld_docs[] =
   "helloworld( ): Any message you want to put here!!\n";

static PyMethodDef helloworld_funcs[] = {
   {"helloworld", (PyCFunction)helloworld, 
      METH_NOARGS, helloworld_docs},
      {NULL}
};

void inithelloworld(void) {
   Py_InitModule3("helloworld", helloworld_funcs,
                  "Extension module example!");
}

Aquí, la función Py_BuildValue se usa para construir un valor de Python. Guarde el código anterior en el archivo hello.c. Veríamos cómo compilar e instalar este módulo para ser llamado desde el script de Python.

Creación e instalación de extensiones

El paquete distutils hace que sea muy fácil distribuir módulos de Python, tanto Python puro como módulos de extensión, de forma estándar. Los módulos se distribuyen en forma de código fuente y se construyen e instalan mediante un script de configuración normalmente llamado setup.py de la siguiente manera.

Para el módulo anterior, debe preparar el siguiente script setup.py:

from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',  \
      ext_modules=[Extension('helloworld', ['hello.c'])])

Ahora, use el siguiente comando, que realizaría todos los pasos necesarios de compilación y vinculación, con los comandos e indicadores correctos del compilador y vinculador, y copie la biblioteca dinámica resultante en un directorio apropiado:

$ python setup.py install

En los sistemas basados ​​en Unix, lo más probable es que necesite ejecutar este comando como root para tener permisos para escribir en el directorio site-packages. Por lo general, esto no es un problema en Windows.

Importación de extensiones

Una vez que haya instalado su extensión, podrá importar y llamar a esa extensión en su secuencia de comandos de Python de la siguiente manera:

#!/usr/bin/python
import helloworld

print helloworld.helloworld()

Esto produciría el siguiente resultado:

Hello, Python extensions!!

Pasar parámetros de función

Como probablemente querrá definir funciones que acepten argumentos, puede usar una de las otras firmas para sus funciones C. Por ejemplo, la siguiente función, que acepta cierto número de parámetros, se definiría así:

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Parse args and do something interesting here. */
   Py_RETURN_NONE;
}

La tabla de métodos que contiene una entrada para la nueva función se vería así:

static PyMethodDef module_methods[] = {
   { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
   { "func", module_func, METH_VARARGS, NULL },
   { NULL, NULL, 0, NULL }
};

Puede usar la función API PyArg_ParseTuple para extraer los argumentos del puntero PyObject pasado a su función C.

El primer argumento de PyArg_ParseTuple es el argumento args. Este es el objeto que analizará . El segundo argumento es una cadena de formato que describe los argumentos tal como espera que aparezcan. Cada argumento está representado por uno o más caracteres en la cadena de formato de la siguiente manera.

static PyObject *module_func(PyObject *self, PyObject *args) {
   int i;
   double d;
   char *s;

   if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
      return NULL;
   }
   
   /* Do something interesting here. */
   Py_RETURN_NONE;
}

Compilar la nueva versión de su módulo e importarla le permite invocar la nueva función con cualquier número de argumentos de cualquier tipo:

module.func(1, s="three", d=2.0)
module.func(i=1, d=2.0, s="three")
module.func(s="three", d=2.0, i=1)

Probablemente puedas inventar incluso más variaciones.

La función PyArg_ParseTuple

Aquí está la firma estándar para PyArg_ParseTuple función -

int PyArg_ParseTuple(PyObject* tuple,char* format,...)

Esta función devuelve 0 para errores y un valor no igual a 0 para éxito. tuple es el PyObject * que era el segundo argumento de la función C. Aquí el formato es una cadena C que describe argumentos obligatorios y opcionales.

Aquí hay una lista de códigos de formato para PyArg_ParseTuple función -

Código Tipo C Sentido
C carbonizarse Una cadena de Python de longitud 1 se convierte en un carácter C.
re doble Un flotador de Python se convierte en un doble de C.
F flotador Un flotador Python se convierte en un flotador C.
yo En t Un int de Python se convierte en un int de C.
l largo Un int de Python se convierte en una C larga.
L largo largo Un int de Python se convierte en una C long long
O PyObject * Obtiene una referencia prestada no NULL al argumento de Python.
s carbonizarse* Cadena de Python sin nulos incrustados en C char *.
s # char * + int Cualquier cadena de Python a la dirección y longitud de C.
t # char * + int Búfer de un solo segmento de solo lectura a la dirección C y la longitud.
tu Py_UNICODE * Python Unicode sin nulos incrustados en C.
tu # Py_UNICODE * + int Cualquier dirección y longitud de Python Unicode C.
w # char * + int Búfer de lectura / escritura de un solo segmento en dirección C y longitud.
z carbonizarse* Como s, también acepta None (establece C char * en NULL).
z # char * + int Como s #, también acepta None (establece C char * en NULL).
(...) según ... Una secuencia de Python se trata como un argumento por elemento.
|   Los siguientes argumentos son opcionales.
:   Fin de formato, seguido del nombre de la función para los mensajes de error.
;   Fin de formato, seguido del texto completo del mensaje de error.

Devolución de valores

Py_BuildValue toma una cadena de formato muy similar a PyArg_ParseTuple . En lugar de pasar las direcciones de los valores que está construyendo, pasa los valores reales. Aquí hay un ejemplo que muestra cómo implementar una función de adición:

static PyObject *foo_add(PyObject *self, PyObject *args) {
   int a;
   int b;

   if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
      return NULL;
   }
   return Py_BuildValue("i", a + b);
}

Así es como se vería si se implementara en Python:

def add(a, b):
   return (a + b)

Puede devolver dos valores de su función de la siguiente manera, esto se capturaría usando una lista en Python.

static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
   int a;
   int b;

   if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
      return NULL;
   }
   return Py_BuildValue("ii", a + b, a - b);
}

Así es como se vería si se implementara en Python:

def add_subtract(a, b):
   return (a + b, a - b)

La función Py_BuildValue

Aquí está la firma estándar para Py_BuildValue función -

PyObject* Py_BuildValue(char* format,...)

Aquí el formato es una cadena C que describe el objeto Python que se va a construir. Los siguientes argumentos de Py_BuildValue son valores de C a partir de los cuales se construye el resultado. El resultado de PyObject * es una nueva referencia.

La siguiente tabla enumera las cadenas de código de uso común, de las cuales cero o más se unen en formato de cadena.

Código Tipo C Sentido
C carbonizarse AC char se convierte en una cadena de Python de longitud 1.
re doble AC double se convierte en un flotador Python.
F flotador El flotador de CA se convierte en un flotador de Python.
yo En t AC int se convierte en Python int.
l largo AC long se convierte en un int de Python.
norte PyObject * Pasa un objeto Python y roba una referencia.
O PyObject * Pasa un objeto Python y lo INCREF de la forma habitual.
O & convertir + anular * Conversión arbitraria
s carbonizarse* C 0-terminado char * a una cadena de Python, o NULL a None.
s # char * + int C char * y la longitud de la cadena de Python, o NULL a None.
tu Py_UNICODE * Cadena de ancho C, terminada en nulo a Python Unicode, o NULL a None.
tu # Py_UNICODE * + int Cadena de ancho C y longitud a Python Unicode, o NULL a None.
w # char * + int Búfer de lectura / escritura de un solo segmento en dirección C y longitud.
z carbonizarse* Como s, también acepta None (establece C char * en NULL).
z # char * + int Como s #, también acepta None (establece C char * en NULL).
(...) según ... Crea una tupla de Python a partir de valores C.
[...] según ... Crea una lista de Python a partir de valores C.
{...} según ... Crea un diccionario Python a partir de valores C, alternando claves y valores.

Code {...} construye diccionarios a partir de un número par de valores C, alternativamente claves y valores. Por ejemplo, Py_BuildValue ("{issi}", 23, "zig", "zag", 42) devuelve un diccionario como {23: 'zig', 'zag': 42} de Python.