Para determinadas aplicaciones, la eficiencia y el rendimiento es clave. Python es un buen lenguaje para programar rápido y mucho en pocas líneas, pero a veces queremos bajar más de nivel para ofrecer mejores prestaciones.
Pero esta decisión no debe implicar, perder nuestra comodidad que tenemos con Python. Existe una biblioteca incluida nativamente desde Python 2.5 llamada ctypes que nos permite utilizar funciones y bibliotecas compartidas de C (en cualquier sistema operativo) obteniendo la eficiencia del lenguaje C pero sin renunciar a las bondades de sintaxis de Python.
Ejemplo práctico y sencillo
Veamos un ejemplo sencillo para ilustrar esta gran funcionalidad. Para empezar podemos crear un simple archivo de código fuente en C y por ejemplo llamarlo ‘libtest.c’.
Por ejemplo, en esta biblioteca C, haremos una simple función que multiplique enteros:
int multiply(int num1, int num2)
{
return num1 * num2;
}
Ahora necesitaremos compilar nuestra biblioteca ‘libtest.c’ en una biblioteca válida, para ello utilizaremos los siguientes comandos de compilación:
gcc -c -fPIC libtest.c
gcc -shared libtest.o -o libtest.so
Ello creara una versión final de la biblioteca para GNU/Linux llamada libtest.so que podamos usar en Python.
Ahora probemos nuestra biblioteca (debes poner el path o camino de libtest.so en el código para que funcione o incluirlo en las rutas donde GNU/Linux buscara las bibliotecas comunes):
# A partir de Python >= 2.5 se incluye la libreria *ctypes* en el core, comprobar si esta disponible
import sys
try:
from ctypes import *
except ImportError:
print 'ERROR! La biblioteca *ctypes* para Python no esta disponible.'
sys.exit(-1)
libtest = cdll.LoadLibrary('ruta_a_la_biblioteca/libtest.so')
print 'La multiplicación de 2 * 2 es:', libtest.multiply(2, 2)
Este ejemplo imprimirá 4 si todo ha ido correctamente. Como se aprecia, no es nada complicado usar código C nativo en Python.
Ejemplo con parámetros
Podemos crear ejemplos más complejos, incluso con funciones que tengan argumentos como punteros o pasados por referencia. Por ejemplo una lista donde se sumen todos los valores, su media:
/* suma de una lista de valores */
double sum(double *data, int n)
{
int i=0;
double sum=0.0;
for (i=0; i < n; i++)
sum += data[i];
return sum;
}
/* media de una lista de valores */
double mean(double *data, int n)
{
double s = sum(data, n);
return s/((double)n);
}
Lo compilamos de nuevo con:t:
$ gcc -shared -fPIC liblist.c -o liblist.so
Y en python de nuevo usamos ctypes:
from ctypes import *
so = CDLL("liblist.so")
# Establecemos las interfaces con los argumentos
# y los valores que devuelven
so.mean.argtypes= [POINTER(c_double), c_int]
so.mean.restype = c_double
so.sum.argtypes = [POINTER(c_double), c_int]
so.sum.restype = c_double
# Llamamos a las funciones
def cmean(self, dlist):
doubleArray = c_double*len(dlist) # Tipo de dato (double[n])
cx = doubleArray(*dlist) # Array actual
n = len(cx) # Longitud del dato
result = so.mean(cx, n)
return float(result)
def csum(self, dlist):
doubleArray = c_double*len(dlist)
cx = doubleArray(*dlist)
n = len(cx)
result = so.sum(cx, n)
return float(result)
# Ahora podemos usar estas funciones como si fuera python puro!
data = [1,2,3,4,5,6]
print cmean(data)
print csum(data)
Con ctypes podemos crear un wrapper (envoltura o adaptador) para una biblioteca matemática u otras funciones que nos interesen.
Ejemplo con MPI
Otro ejemplo práctico sería poder escribir MPI en python desde C, como el ejemplo que escribí hace unos días. Notad que ya existen implementaciones python de MPI, pero nunca esta demás saber como funcionan y que principios siguen, por ejemplo para iniciarse en openMPI desde python, podríamos hacer:
#!/usr/bin/python
import sys
try:
from ctypes import CDLL, pythonapi, c_int, POINTER, c_char_p, byref, RTLD_GLOBAL
from ctypes.util import find_library
except ImportError:
print 'ERROR! La biblioteca *ctypes* para Python no esta disponible!'
sys.exit(-1)
libc = CDLL('libc.so.6')
print libc._handle, libc._name
f = pythonapi.Py_GetArgcArgv
argc = c_int()
argv = POINTER(c_char_p)()
f(byref(argc), byref(argv))
mpi = CDLL(find_library('mpi'), RTLD_GLOBAL)
print mpi._handle, mpi._name
libc.printf("Hola mundo");
mpi.MPI_Finalize()
En este ejemplo, utilizo el método find_library() de ctypes que me permite olvidarme de localizar la ruta de una biblioteca y así buscar por si mismo el programa su ruta. Se importa la biblioteca de C y la de MPI y se hace un simple hola mundo mostrando algunos datos sobre las bibliotecas también. Para ctypes únicamente he importando las funciones necesarias, evitando que se cargue toda la biblioteca.
En definitiva ctypes es una potente herramienta que todo desarrollador Python debería tener en cuenta cuando quiera realizar desarrollos más eficientes y aprovechar implementaciones de otras bibliotecas compartidas.