Cómo crear fácilmente bindings de Python para una librería C++ usando SIP
¿Tienes algunas librerías en C++ hechas y no sabes cómo utilizarlas en Python? ¿Estás "decepcionado" porque tu programa construído totalmente en Python no es tan rápido como esperabas?. La solución es "bien sencilla": te programas una librería en C/C++ (si no la tienes) que haga el trabajo, construyes un wrapper para utilizarla en Python y... ¡listo!
Para el primer paso no hay, de momento, solución automática. Pero para el segundo existen diversas herramientas que ayudan a la construcción del wrapper, su compilación e instalación. Una de estas herramientas, la que vamos a utilizar en esta receta, se llama python-sip. Asiste a la creación de los wrappers para librerías C/C++, aunque los ejemplos que se mostrarán serán de C++. Para más información, consulta la sección de Enlaces.
# apt-get install python-sip4 sip4
Para ilustrar el funcionamiento de python-sip, ¡qué mejor que un ejemplo!. Supón que la librería consta de una clase construída en C++ y cuyo fichero de cabecera (Impresora.h) es el siguiente:
#include <iostream> #include <set> using namespace std; class Impresora { public: Impresora(const string&); ~Impresora(); void imprimirLista(set<string>); private: string nombre; };
Y cuya implementación es la siguiente (Impresora.cpp):
#include <Impresora.h> Impresora::Impresora(const string& name) { nombre = name; cout << nombre << endl; } Impresora::~Impresora() {} void Impresora::imprimirLista(set<string> lista) { set<string>::iterator it; for ( it=lista.begin() ; it != lista.end(); it++ ) cout << " " << *it; cout << "<--- Imprime " << nombre << endl; }
Como se puede apreciar, la clase tiene poca historia: tiene un atributo privado ("nombre") y un método para imprimir una lista (conjunto) de strings ordenados alfabéticamente. Para compilar esta librería tenemos el siguiente Makefile:
CXXFLAGS = -I. -fPIC LFLAGS = -shared all: libImpresora.so libImpresora.so: Impresora.o $(CC) $(CXXFLAGS) $(LFLAGS) $^ -o $@
En definitiva, partimos de un archivo "libImpresora.so" que contiene la librería en C++ ya compilada. Ahora comienza la construcción del wrapper con ayuda de python-sip4.
Comenzamos creando un archivo que, por convenio, llamaremos "Impresora.sip". En este fichero especificaremos la estructura del wrapper con sintaxis sip, muy parecida a C++. Una primera aproximación sería la siguiente:
%Module Ejemplos 0 class Impresora { %TypeHeaderCode #include < Impresora.h > %End public: Impresora(const char *); ~Impresora(); void imprimirLista(SIP_PYLIST); };
SIP_PYLIST representa a una lista de Python pero, ¿cómo se hará la correspondecia set->PythonList?. ¿Sip se encargará de ello?. Pues no. La correspondencia entre tipos complejos (objetos propios, clases auxiliares...) debe hacerse de forma explícita. Para especificar la conversión de uno a otro utilizaremos la directiva %MethodCode:
%Module Ejemplos 0 class Impresora { %TypeHeaderCode #include <Impresora.h> %End public: Impresora(const char *); ~Impresora(); void imprimirLista(SIP_PYLIST); %MethodCode set<std::string> arg; for(int i = 0; i < PyList_Size(a0); ++i) { char* value = PyString_AsString(PyList_GetItem(a0,i)); arg.insert(, value); } sipCpp->imprimirLista(arg); %End };
Una vez tenemos la especificación del fichero .sip, es hora de construir el wrapper de forma automática. Para ello, es aconsejable utilizar un pequeño script en python que genera los archivos .cpp y el Makefile final. Un fichero básico sería el siguiente:
import os import sipconfig # The name of the SIP build file generated by SIP and used by the build # system. build_file = "impresora.sbf" # Get the SIP configuration information. config = sipconfig.Configuration() # Run SIP to generate the code. os.system(" ".join([config.sip_bin, "-c", ".", "-b", build_file, "Impresora.sip"])) # Create the Makefile. makefile = sipconfig.SIPModuleMakefile(config, build_file) # Add the library we are wrapping. The name doesn't include any platform # specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the # ".dll" extension on Windows). makefile.extra_libs = ["Impresora"] makefile.extra_lflags = ["-L."] # Generate the Makefile itself. makefile.generate()
El código de este script es "autocomentado". Supongamos que el fichero que contiene el código anterior se llama "configure.py":
$ python configure.py
El comando anterior genera el wrapper en C++ y el Makefile que construirá todo.
$ make
La compilación del wrapper generará un archivo "Ejemplos.so", que es la librería compartida que será utilizada por Python.
Tenemos 2 opciones:
Cualquiera que sea el modo de prueba se debe modificar la variable de entorno LD_LIBRARY_PATH para que el intérprete de Python pueda localizar el "libImpresora.so". A continuación, un ejemplo utilizando iPython:
~/pruebas/python-sip$ export LD_LIBRARY_PATH=.
~/pruebas/python-sip$ ipython
In [1]: import Ejemplos
In [2]: imp = Ejemplos.Impresora("HAL9000")
In [4]: imp.imprimirLista(["Dave", "tengo", "miedo"])
Dave miedo tengo<--- Imprime HAL9000
Comments
muy bien...
… enhorabuena por la receta.
--
“Computer Science is no more about computers than astronomy is about telescopes.”
– Edsger W. Dijkstra
brue