Cuando atacamos un problema con un lenguaje de programación relativamente nuevo, es razonable pensar que ya habrá código escrito en lenguajes más antiguos. De hecho, en muchos casos nos encontramos con que existen librerías en C que resuelven parte de nuestro problema. En Python, existen varias formas de reutilizar código escrito en C/C++. Hasta donde yo sé, cuatro herramientas diferentes: Python/C API, SWIG, Ctypes y Cython. He aquí una tabla comparativa extraída de Python Bindings Overview:
Entre los parámetros que considera, está la curva de aprendizaje (cuán sencillo es de aprender a utilizar), lo pythónico que es (si el código Python resultante es de mayor o menor pureza, por así decirlo), si soporta C, si soporta C++, si existe para otros lenguajes de programación y si maneja excepciones. En este artículo vamos a centrarnos en Ctypes ya que, como podéis observar, es la herramienta más sencilla y más pythónica. El único punto en contra es que no soporta C++ (tampoco maneja excepciones, lógicamente, porque C no tiene excepciones).
Ctypes es una FFI (Foreign Function Interface) que forma parte de Python desde la versión 2.5. Nos proporciona mecanismos muy sencillos para realizar llamadas a funciones de C en nuestro código Python y tratar con tipos de datos nativos de C. A continuación, vamos a poner un ejemplo muy sencillo (es una bobada, aviso), pero suficiente para mostrar el potencial de Ctypes.
Supongamos que tenemos una función escrita en C que calcula la media de dos números:
[code lang=»c»]#Archivo calc_mean.c
double mean(double a, double b) {
return (a+b)/2;
}[/code]
Para reutilizar esta función en nuestro programa en Python, realizaremos los siguientes pasos:
- Compilar el código C en una librería compartida.
- Importar la librería compartida mediante Ctypes.
- Asignar los tipos de datos de entrada y salida que acepta la función.
- Utilizar la función con total normalidad, como si de una función de Python se tratase.
Crear una librería compartida
Esta tarea se puede realizar de forma directa mediante GCC, por ejemplo. Sin embargo, he encontrado una manera mucho más sencilla y elegante: mediante SCons. Consiste en crear un archivo llamado SConstruct
en el mismo directorio donde se encuentran los archivos fuente de C (en este caso, en el mismo directorio que nuestro calc_mean.c
) con el siguiente contenido:
[code lang=»python»]env=Environment()
env.Replace(CFLAGS=[‘-O2′,’-Wall’,’-ansi’,’-pedantic’])
env.SharedLibrary(‘libmean.so’, [‘calc_mean.c’])[/code]
Como se puede deducir, la primera línea es obligatoria, la segunda especifica las flags que utilizará GCC (no hace falta tocarlo) y la tercera indica que el archivo calc_mean.c
se convertirá en una librería compartida con el nombre libmean.so
.
Por último, basta con teclear en una terminal —en el mismo directorio— scons
. Y listo: ya tenemos nuestra librería compartida.
Utilizar la librería compartida
Lo más directo es ver un código de ejemplo comentado.
[code lang=»python»]# Archivo ejemplo.py
# Importamos Ctypes
from ctypes import *
# Importamos la librería compartida
libmean = CDLL(‘./libmean.so’)
# Asignamos la función a una variable más corta y manejable
mean = libmean.mean
# Especificamos los tipos C que acepta como entrada
mean.argtypes = [c_double, c_double]
# Especificamos el tipo C que devuelve
mean.restype = c_double
# Lista para su utilización
print mean(3,8)
# Devuelve 5.5[/code]
¿Y si la compilación es un asunto más complejo que un único archivo, «empeora» mucho la sintaxis de SConstruct.
@RinzeWind: No he hecho pruebas todavía, ni me he mirado la documentación de SCons, pero supongo que habrá que añadir una línea más por cada librería compartida y, en cada librería compartida, especificar todos los archivos que la componen. Desde luego, menos complejo que hacerlo invocando directamente a GCC.