Programación de Extensiones en Python con C: Crea Módulos Nativos Rápidos
Cualquier código escrito en lenguajes compilados como C, C++ o Java puede integrarse o importarse en otro script Python. Este código se considera una "extensión".
Un módulo de extensión Python no es más que una librería C normal. En Unix terminan en .so (shared object); en Windows en .dll (dynamically linked library).
Prerrequisitos para Escribir Extensiones
Necesitas los archivos de cabecera de Python:
- Unix: Instala paquete de desarrollo (ej. python3-dev).
- Windows: Incluidos en el instalador binario de Python.
Se asume buen conocimiento de C/C++ para escribir extensiones Python con C.
Primera Vista a una Extensión Python
Agrupa tu código en cuatro partes:
- Archivo de cabecera Python.h.
- Funciones C a exponer como interfaz del módulo.
- Tabla de mapeo: nombres de funciones (como las ven desarrolladores Python) a funciones C internas.
- Función de inicialización.
Archivo de Cabecera Python.h
Incluye Python.h en tu código C para acceder a la API interna de Python y enganchar tu módulo al intérprete.
Incluye Python.h antes que cualquier otra cabecera. Luego siguen las funciones a llamar desde Python.
Las Funciones C
Las firmas de implementación C siempre toman una de tres formas:
static PyObject *MyFunction(PyObject *self, PyObject *args); static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw); static PyObject *MyFunctionWithNoArgs(PyObject *self);
Todas retornan un objeto Python. No existe void en Python como en C. Si no quieres retornar valor, retorna el equivalente C de None usando macro Py_RETURN_NONE.
Nombres de funciones C pueden ser cualesquiera (son static y no se ven fuera). Convención: combinar nombre de módulo y función:
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Tu código aquí */
Py_RETURN_NONE;
}
Tabla de Mapeo de Métodos
Array de estructuras PyMethodDef:
struct PyMethodDef {
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc;
};
Miembros:
- ml_name: Nombre de la función en Python.
- ml_meth: Dirección de función C con una de las firmas arriba.
- ml_flags: Qué firma usa (usualmente METH_VARARGS). Puede combinarse con METH_KEYWORDS para argumentos por palabra clave, o METH_NOARGS para sin argumentos.
- ml_doc: Docstring de la función (puede ser NULL).
La tabla termina con centinela {NULL, NULL, 0, NULL}.
Ejemplo
static PyMethodDef module_methods[] = {
{"func", (PyCFunction)module_func, METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
Función de Inicialización
Llamada por el intérprete Python al cargar el módulo. Debe llamarse init
Debe exportarse desde la librería. Python headers definen PyMODINIT_FUNC para las incantaciones apropiadas según entorno de compilación.
Estructura general:
PyMODINIT_FUNC initModule() {
Py_InitModule3("func", module_methods, "docstring...");
}
Py_InitModule3 parámetros: func (función a exportar), module_methods (tabla de mapeo), docstring (comentario del módulo).
Ejemplo Completo: Hola Mundo
#includestatic 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!"); }
Py_BuildValue construye un valor Python. Guarda en hello.c. Veamos cómo compilar e instalar.
Compilación e Instalación de Extensiones
El paquete distutils facilita distribuir módulos Python (puros y extensiones) de forma estándar. Los módulos se distribuyen en código fuente y se compilan/instalan via script setup.py.
Para el módulo anterior, setup.py:
from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',
ext_modules=[Extension('helloworld', ['hello.c'])])
Comando para compilar, enlazar e instalar:
$ python setup.py install
En sistemas Unix, ejecuta como root para permisos en site-packages. En Windows no suele ser problema.
Importar Extensiones
Una vez instalada, importa y llama desde Python:
#!/usr/bin/python import helloworld print(helloworld.helloworld())
Resultado: Hello, Python extensions!!
Paso de Parámetros a Funciones
Para funciones que aceptan argumentos, usa firma con args:
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Parsear args y hacer algo interesante */
Py_RETURN_NONE;
}
Entrada en tabla de métodos con METH_VARARGS:
static PyMethodDef module_methods[] = {
{"func", module_func, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
};
Usa API PyArg_ParseTuple para extraer argumentos del puntero PyObject:
static PyObject *module_func(PyObject *self, PyObject *args) {
int i; double d; char *s;
if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
return NULL;
}
/* Hacer algo interesante */
Py_RETURN_NONE;
}
Permite invocar: module.func(1, s="three", d=2.0), module.func(i=1, d=2.0, s="three"), etc.
Función PyArg_ParseTuple
Firma estándar:
int PyArg_ParseTuple(PyObject* tuple, char* format, ...)
Retorna 0 para error, ≠0 para éxito. tuple es el segundo argumento de la función C. format describe argumentos obligatorios y opcionales.
Códigos de Formato Comunes
| Código | Tipo C | Significado |
|---|---|---|
| c | char | String Python longitud 1 → C char |
| d | double | Float Python → C double |
| f | float | Float Python → C float |
| i | int | Int Python → C int |
| l | long | Int Python → C long |
| L | long long | Int Python → C long long |
| O | PyObject* | Referencia prestada no-NULL a argumento Python |
| s | char* | String Python sin nulls embebidos → C char* |
| s# | char*+int | Cualquier string Python → dirección C y longitud |
| z | char* | Como s, pero acepta None (pone C char* a NULL) |
| (...) | según ... | Secuencia Python tratada como un argumento por item |
| | | Los siguientes argumentos son opcionales | |
| : | Fin de formato, seguido de nombre función para mensajes error | |
| ; | Fin de formato, seguido de texto completo mensaje error |
Retorno de Valores
Py_BuildValue toma string de formato como PyArg_ParseTuple, pero pasas valores reales (no direcciones).
Ejemplo: Función Suma
static PyObject *foo_add(PyObject *self, PyObject *args) {
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("i", a + b);
}
Equivalente Python: def add(a, b): return a + b
Retornar Múltiples Valores
static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("ii", a + b, a - b);
}
En Python se captura como tupla/lista: def add_subtract(a, b): return (a + b, a - b)
Función Py_BuildValue
Firma estándar:
PyObject* Py_BuildValue(char* format, ...)
format describe el objeto Python a construir. Argumentos siguientes son valores C. Retorna nueva referencia PyObject*.
Códigos Comunes
| Código | Tipo C | Significado |
|---|---|---|
| c | char | C char → string Python longitud 1 |
| d | double | C double → float Python |
| f | float | C float → float Python |
| i | int | C int → int Python |
| l | long | C long → int Python |
| N | PyObject* | Pasa objeto Python y roba referencia |
| O | PyObject* | Pasa objeto Python e INCREF normal |
| O& | convert+void* | Conversión arbitraria |
| s | char* | C char* 0-terminado → string Python, o NULL → None |
| s# | char*+int | C char* y longitud → string Python, o NULL → None |
| u | Py_UNICODE* | C wide string null-terminado → Unicode Python, o NULL → None |
| u# | Py_UNICODE*+int | C wide string y longitud → Unicode Python, o NULL → None |
| z | char* | Como s, acepta None |
| (...) | según ... | Construye tupla Python desde valores C |
| [...] | según ... | Construye lista Python desde valores C |
| {...} | según ... | Construye diccionario Python desde valores C (clave, valor alternados) |
Código {...} construye diccionarios de número par de valores C, alternando claves y valores. Ejemplo: Py_BuildValue("{issi}", 23, "zig", "zag", 42) retorna diccionario como {23: 'zig', 'zag': 42}.
python
- Dominando la Programación Orientada a Objetos en Python: Clases, Herencia, Encapsulación y Polimorfismo
- Función print() en Python: Guía Práctica con Ejemplos
- Domine el Método strip() de Python: Guía Completa y Ejemplos Prácticos
- Domine str.count() en Python: Cómo Contar Caracteres y Subcadenas con Ejemplos
- Domine el Formateo de Strings en Python con format(): Ejemplos Claros
- Domine el Método String.find() en Python: Sintaxis, Ejemplos y Alternativas
- Dominando las funciones lambda en Python: ejemplos prácticos y mejores prácticas
- Función round() en Python explicada con ejemplos prácticos
- Dominando la función map() de Python: sintaxis, ejemplos y mejores prácticas
- Python timeit(): medición de tiempo de ejecución con ejemplos prácticos
- Counter de collections en Python: conteo eficiente, actualización y operaciones aritméticas