Bindings Python de librerías C++, con SIP
Cómo crear fácilmente bindings de Python para una librería C++ usando SIP
Introducción
¿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.Ingredientes
# apt-get install python-sip4 sip4
Ejemplo
Librería C++
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;
};
#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;
}
CXXFLAGS = -I. -fPIC
LFLAGS = -shared
all: libImpresora.so
libImpresora.so: Impresora.o
$(CC) $(CXXFLAGS) $(LFLAGS) $^ -o $@
Construcción del wrapper
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);
};
- %Module especifica el módulo en el que va estar incluída la librería, junto con un número de versión. En el ejemplo, el módulo es "Ejemplo" en su versión "0".
- %TypeHeaderCode es necesario para que sip pueda leer la información de la clase que se va a "wrappear". Todo lo encerrado en ese bloque, sip lo utilizará para construir el wrapper con los tipos adecuados.
- Nótese que se ha modificado los tipos de los argumentos del constructor y del método "imprimirLista". Sip no soporta tipos como string (y no digamos sets de la STL de C++). Sin embargo, en el caso de string, basta con especificar de que se trata de un "char*" y C++ se encargará del cambio de tipo. El argumento de tipo set se ha sustituído por una constante de sip que representa una lista de Python. Existe una constante de este tipo para cada uno de los tipos de Python (SIP_PYDICT...). Consúltese las referencias para más información.
- Como se puede apreciar, no está especificado el atributo privado "nombre". Sip no soporta atributos ni métodos privados para la construcción del wrapper.
%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
};
- Declaramos una variable del mismo tipo que el argumento que espera el método, en nuestro caso un set de strings.
- a0 es el argumento (SIP_PYLIST) de tipo PyObject* de la librería CPython. Por tanto, el bucle no sirve nada más que para guardar cada valor de la lista Python en el set.
- sipCpp es un puntero al objeto de entorno, en nuestro caso, "Impresora*". Cuando tenemos el set completamente construído llamamos a "imprimirLista" con el parámetro convertido.
Construcción del Wrapper
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()
$ python configure.py
$ make
Probando
Tenemos 2 opciones:- Ejecutar "make install" y, por tanto, instalaremos el archivo "Ejemplos.so" en /usr/lib/pythonX.X/site-packages donde será accesible para cualquier programa Python que utilize el módulo Ejemplos.
- Probar la librería con iPython, ejecutándolo en el directorio donde se encuentra el nuevo módulo
~/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
Referencias
- Documentación de sip4 en /usr/share/doc/sip4/reference/sipref.html, incluída en el paquete sip4
- CPyton Reference.
[ show comments ]
blog comments powered by Disqus