c++ python swig boost-python

¿Cómo puedo implementar una clase C++ en Python, para ser llamado por C++?



swig boost-python (6)

Basado en la (muy útil) respuesta de Eudoxos, tomé su código y lo extendí de tal manera que ahora hay un intérprete incorporado, con un módulo incorporado.

Esta respuesta es el equivalente de Boost.Python de mi respuesta basada en SWIG .

El archivo de cabecera myif.h:

class myif { public: virtual float myfunc(float a) const { return 0; } virtual ~myif() {} };

Es básicamente como en la pregunta, pero con una implementación predeterminada de myfunc y un destructor virtual.

Para la implementación de Python, MyCl.py básicamente tengo lo mismo que la pregunta:

import myif class MyCl(myif.myif): def myfunc(self,a): return a*2.0

Esto luego deja mymain.cc, la mayoría de los cuales se basa en la respuesta de Eudoxos:

#include <boost/python.hpp> #include <iostream> #include "myif.h" using namespace boost::python; // This is basically Eudoxos''s answer: struct MyIfWrapper: myif, wrapper<myif>{ float myfunc(float a) const { if(this->get_override("myfunc")) return this->get_override("myfunc")(a); else return myif::myfunc(a); } }; BOOST_PYTHON_MODULE(myif){ class_<MyIfWrapper,boost::noncopyable>("myif") .def("myfunc",&myif::myfunc) ; } // End answer by Eudoxos int main( int argc, char ** argv ) { try { // Tell python that "myif" is a built-in module PyImport_AppendInittab("myif", initmyif); // Set up embedded Python interpreter: Py_Initialize(); object main_module = import("__main__"); object main_namespace = main_module.attr("__dict__"); PySys_SetPath("."); main_namespace["mycl"] = import("mycl"); // Create the Python object with an eval() object obj = eval("mycl.MyCl()", main_namespace); // Find the base C++ type for the Python object (from Eudoxos) const myif &b=extract<myif>(obj)(); std::cout << b.myfunc(5) << std::endl; } catch( error_already_set ) { PyErr_Print(); } }

La parte clave que he agregado aquí, más allá de "¿cómo incrusto Python usando Boost.Python?" y "¿cómo extiendo Python usando Boost.python?" (que fue respondida por Eudoxos) es la respuesta a la pregunta "¿Cómo hago ambas cosas a la vez en el mismo programa?". La solución a esto reside en la llamada PyImport_AppendInittab , que toma la función de inicialización que normalmente se llamaría cuando el módulo está cargado y lo registra como un módulo incorporado. Por lo tanto, cuando mycl.py dice " import myif , termina importando el módulo Boost.Python incorporado.

Tengo una interfaz de clase escrita en C ++. Tengo algunas clases que implementan esta interfaz también escrita en C ++. Estos se llaman en el contexto de un programa C ++ más grande, que esencialmente implementa "principal". Quiero poder escribir implementaciones de esta interfaz en Python y permitir que se utilicen en el contexto del programa C ++ más grande, como si acabaran de escribirse en C ++.

Se ha escrito mucho acerca de la interfaz entre Python y C ++, pero no puedo entender cómo hacer lo que quiero. Lo más cercano que puedo encontrar está aquí: http://www.cs.brown.edu/~jwicks/boost/libs/python/doc/tutorial/doc/html/python/exposing.html#python.class_virtual_functions , pero esto no está No del todo bien.

Para ser más concretos, supongamos que tengo una interfaz C ++ existente que se define de la siguiente manera:

// myif.h class myif { public: virtual float myfunc(float a); };

Lo que quiero ser capaz de hacer es algo así como:

// mycl.py ... some magic python stuff ... class MyCl(myif): def myfunc(a): return a*2

Luego, de vuelta en mi código C ++, quiero poder decir algo como:

// mymain.cc void main(...) { ... some magic c++ stuff ... myif c = MyCl(); // get the python class cout << c.myfunc(5) << endl; // should print 10 }

Espero que esto sea lo suficientemente claro;)


Citando http://wiki.python.org/moin/boost.python/Inheritance

"Boost.Python también nos permite representar las relaciones de herencia de C ++ para que se pasen las clases derivadas derivadas donde los valores, punteros o referencias a una clase base se esperan como argumentos".

Hay ejemplos de funciones virtuales para que resuelva la primera parte (la de la clase MyCl (myif))

Para ejemplos específicos de cómo hacer esto, http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions

Para la línea myif c = MyCl (); necesita exponer su python (módulo) a C ++. Aquí hay ejemplos http://wiki.python.org/moin/boost.python/EmbeddingPython



Ejemplo mínimo tenga en cuenta que es complicado por el hecho de que Base no es puramente virtual. Aquí vamos:

  1. baz.cpp:

    #include<string> #include<boost/python.hpp> using std::string; namespace py=boost::python; struct Base{ virtual string foo() const { return "Base.foo"; } // fooBase is non-virtual, calling it from anywhere (c++ or python) // will go through c++ dispatch string fooBase() const { return foo(); } }; struct BaseWrapper: Base, py::wrapper<Base>{ string foo() const{ // if Base were abstract (non-instantiable in python), then // there would be only this->get_override("foo")() here // // if called on a class which overrides foo in python if(this->get_override("foo")) return this->get_override("foo")(); // no override in python; happens if Base(Wrapper) is instantiated directly else return Base::foo(); } }; BOOST_PYTHON_MODULE(baz){ py::class_<BaseWrapper,boost::noncopyable>("Base") .def("foo",&Base::foo) .def("fooBase",&Base::fooBase) ; }

  2. bar.py

    import sys sys.path.append(''.'') import baz class PyDerived(baz.Base): def foo(self): return ''PyDerived.foo'' base=baz.Base() der=PyDerived() print base.foo(), base.fooBase() print der.foo(), der.fooBase()

  3. Makefile

    default: g++ -shared -fPIC -o baz.so baz.cpp -lboost_python `pkg-config python --cflags`

Y el resultado es:

Base.foo Base.foo PyDerived.foo PyDerived.foo

donde se puede ver cómo fooBase() (la función de c ++ no virtual) llama a virtual foo() , que se resuelve con la anulación independientemente de si está en c ++ o python. Podrías derivar una clase de Base en c ++ y funcionaría de la misma manera.

EDITAR (extraer el objeto c ++):

PyObject* obj; // given py::object pyObj(obj); // wrap as boost::python object (cheap) py::extract<Base> ex(pyObj); if(ex.check()){ // types are compatible Base& b=ex(); // get the wrapped object // ... } else { // error } // shorter, thrwos when conversion not possible Base &b=py::extract<Base>(py::object(obj))();

Construya py::object desde PyObject* y use py::extract para consultar si el objeto python coincide con lo que está tratando de extraer: PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor(); PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();


Hay dos partes en esta respuesta. En primer lugar, debe exponer su interfaz en Python de manera que permita que las implementaciones de Python anulen partes de ella a voluntad. Entonces necesita mostrar su programa C ++ (en main cómo llamar a Python.

Exponer la interfaz existente a Python:

La primera parte es bastante fácil de hacer con SWIG. Modifiqué tu escenario de ejemplo ligeramente para solucionar algunos problemas y agregué una función adicional para probar:

// myif.h class myif { public: virtual float myfunc(float a) = 0; }; inline void runCode(myif *inst) { std::cout << inst->myfunc(5) << std::endl; }

Por ahora voy a ver el problema sin incrustar Python en tu aplicación, es decir, comienzas la superación en Python, no en int main() en C ++. Sin embargo, es bastante sencillo agregar eso más tarde.

Primero está funcionando el polimorfismo de lenguaje cruzado :

%module(directors="1") module // We need to include myif.h in the SWIG generated C++ file %{ #include <iostream> #include "myif.h" %} // Enable cross-language polymorphism in the SWIG wrapper. // It''s pretty slow so not enable by default %feature("director") myif; // Tell swig to wrap everything in myif.h %include "myif.h"

Para hacer eso, hemos habilitado la función de director de SWIG globalmente y específicamente para nuestra interfaz. El resto es bastante estándar SWIG sin embargo.

Escribí una implementación de Python de prueba:

import module class MyCl(module.myif): def __init__(self): module.myif.__init__(self) def myfunc(self,a): return a*2.0 cl = MyCl() print cl.myfunc(100.0) module.runCode(cl)

Con eso, pude compilar y ejecutar esto:

swig -python -c++ -Wall myif.i g++ -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7 python mycl.py 200.0 10

Exactamente lo que esperarías ver de esa prueba.

Incrustar Python en la aplicación:

A continuación, tenemos que implementar una versión real de su mymain.cc. He preparado un boceto de lo que podría ser:

#include <iostream> #include "myif.h" #include <Python.h> int main() { Py_Initialize(); const double input = 5.0; PyObject *main = PyImport_AddModule("__main__"); PyObject *dict = PyModule_GetDict(main); PySys_SetPath("."); PyObject *module = PyImport_Import(PyString_FromString("mycl")); PyModule_AddObject(main, "mycl", module); PyObject *instance = PyRun_String("mycl.MyCl()", Py_eval_input, dict, dict); PyObject *result = PyObject_CallMethod(instance, "myfunc", (char *)"(O)" ,PyFloat_FromDouble(input)); PyObject *error = PyErr_Occurred(); if (error) { std::cerr << "Error occured in PyRun_String" << std::endl; PyErr_Print(); } double ret = PyFloat_AsDouble(result); std::cout << ret << std::endl; Py_Finalize(); return 0; }

Básicamente es la incrustación estándar de Python en otra aplicación . Funciona y ofrece exactamente lo que espera ver también:

g++ -Wall -Wextra -I/usr/include/python2.7 main.cc -o main -lpython2.7 ./main 200.0 10 10

La última pieza del rompecabezas es poder convertir el PyObject* que obtienes al crear la instancia en Python en un myif * . SWIG nuevamente lo hace razonablemente sencillo.

Primero tenemos que pedirle a SWIG que exponga su tiempo de ejecución en un archivo de cabecera para nosotros. Hacemos esto con una llamada adicional a SWIG:

swig -Wall -c++ -python -external-runtime runtime.h

A continuación, tenemos que volver a compilar nuestro módulo SWIG, dando explícitamente la tabla de tipos que SWIG conoce sobre un nombre para que podamos buscarlo desde nuestro main.cc. Recopilamos el .so usando:

g++ -DSWIG_TYPE_TABLE=myif -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7

Luego agregamos una función auxiliar para convertir el PyObject* a myif* en nuestro main.cc:

#include "runtime.h" // runtime.h was generated by SWIG for us with the second call we made myif *python2interface(PyObject *obj) { void *argp1 = 0; swig_type_info * pTypeInfo = SWIG_TypeQuery("myif *"); const int res = SWIG_ConvertPtr(obj, &argp1,pTypeInfo, 0); if (!SWIG_IsOK(res)) { abort(); } return reinterpret_cast<myif*>(argp1); }

Ahora que está en su lugar, podemos usarlo desde main() :

int main() { Py_Initialize(); const double input = 5.5; PySys_SetPath("."); PyObject *module = PyImport_ImportModule("mycl"); PyObject *cls = PyObject_GetAttrString(module, "MyCl"); PyObject *instance = PyObject_CallFunctionObjArgs(cls, NULL); myif *inst = python2interface(instance); std::cout << inst->myfunc(input) << std::endl; Py_XDECREF(instance); Py_XDECREF(cls); Py_Finalize(); return 0; }

Finalmente tenemos que compilar main.cc con -DSWIG_TYPE_TABLE=myif y esto da:

./main 11


No hay una manera real de interconectar el código de C ++ directamente con Python.

SWIG maneja esto, pero construye su propio contenedor.

Una alternativa que prefiero sobre SWIG es ctypes, pero para usarla necesitas crear un contenedor C.

Por el ejemplo:

// myif.h class myif { public: virtual float myfunc(float a); };

Construye un contenedor de C de la siguiente manera:

extern "C" __declspec(dllexport) float myif_myfunc(myif* m, float a) { return m->myfunc(a); }

Como está compilando usando C ++, la "C" externa permite la vinculación C para que pueda llamarla fácilmente desde su dll, y __declspec (dllexport) permite llamar a la función desde la dll.

En Python:

from ctypes import * from os.path import dirname dlldir = dirname(__file__) # this strips it to the directory only dlldir.replace( ''//', ''////' ) # Replaces / with // in dlldir lib = cdll.LoadLibrary(dlldir+''//myif.dll'') # Loads from the full path to your module. # Just an alias for the void pointer for your class c_myif = c_void_p # This tells Python how to interpret the return type and arguments lib.myif_myfunc.argtypes = [ c_myif, c_float ] lib.myif_myfunc.restype = c_float class MyCl(myif): def __init__: # Assume you wrapped a constructor for myif in C self.obj = lib.myif_newmyif(None) def myfunc(a): return lib.myif_myfunc(self.obj, a)

Mientras SWIG hace todo esto por ti, hay poco espacio para que puedas modificar las cosas a tu gusto sin frustrarte con todos los cambios que tienes que rehacer cuando vuelves a generar el contenedor SWIG.

Un problema con los ctypes es que no maneja las estructuras de STL, ya que está hecho para C. SWIG maneja esto por usted, pero puede ser capaz de envolverlo usted mismo en el C. Depende de usted.

Aquí está el documento de Python para ctypes:

http://docs.python.org/library/ctypes.html

Además, el dll incorporado debe estar en la misma carpeta que su interfaz de Python (¿por qué no sería?).

Sin embargo, tengo curiosidad, ¿por qué querrías llamar a Python desde dentro de C ++ en lugar de llamar directamente a la implementación de C ++?