37 votes

Comment puis-je implémenter une classe C ++ en Python, à appeler par C ++?

J'ai une classe d'interface écrite en C++. J'ai un peu de classes qui implémentent cette interface est également écrit en C++. Ceux-ci sont appelés dans le cadre d'un vaste programme en C++, qui est essentiellement mise en œuvre des "principaux". Je veux être capable d'écrire des implémentations de cette interface en Python, et leur permettent d'être utilisés dans le cadre plus large du programme en C++, comme si elles avaient été écrites en C++.

Il y a beaucoup écrit au sujet de l'interfaçage python et C++, mais je ne peux pas comprendre comment faire ce que je veux. Le plus proche que je peux trouver ici: http://www.cs.brown.edu/~jwicks/boost/libs/python/doc/tutorial/doc/html/python/exposing.html#python.class_virtual_functionsmais ce n'est pas tout à fait droit.

Pour être plus concret, supposons que j'ai déjà une interface C++ défini quelque chose comme:

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

Ce que je veux être en mesure de faire est quelque chose comme:

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

Puis, de retour dans mon code C++, je veux être en mesure de dire quelque chose comme:

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

J'espère que c'est suffisamment clair ;)

39voto

Flexo Points 39273

Il y a deux parties à cette réponse. D'abord, vous devez exposer votre interface en Python d'une manière qui permet implémentations de Python pour remplacer les pièces de à volonté. Ensuite, vous devez montrer votre programme C++ (en main comment appeler Python.


Exposer l'interface existante pour Python:

La première partie est assez facile à faire avec SWIG. J'ai modifié votre exemple de scénario légèrement pour corriger quelques problèmes et a ajouté une fonction supplémentaire pour les tests:

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

inline void runCode(myif *inst) {
  std::cout << inst->myfunc(5) << std::endl;
}

Pour l'instant, je vais regarder le problème sans l'incorporation de Python dans votre application, c'est à dire de commencer excetion en Python, et non pas en int main() en C++. Il est relativement simple d'ajouter que, plus tard bien que.

La première est l'obtention de la croix de langue polymorphisme de travail:

%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"

Pour ce faire, nous avons activé RASADE de directeur de la fonctionnalité à l'échelle mondiale et en particulier pour notre interface. Le reste est assez standard RASADE bien.

J'ai écrit un test Python de mise en œuvre:

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)

Avec qui j'étais alors en mesure de compiler et d'exécuter ceci:

swig -python -c++ -Wall myif.j' 
g++ -Wall-Wextra -shared-o _module.donc myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7

python mycl.py 
200.0
10

Exactement ce que vous espérer de voir à partir de ce test.


L'incorporation de l'Python dans l'application:

Ensuite, nous avons besoin de mettre en œuvre un réel version de votre mymain.cc. J'ai mis en place une esquisse de ce à quoi il pourrait ressembler:

#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;
}

Il est fondamentalement juste norme intégration de Python dans une autre application. Il fonctionne et donne exactement ce que vous espérer voir aussi:

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

La dernière pièce du puzzle est en mesure de convertir l' PyObject* que vous obtenez à partir de la création de l'instance en Python en myif *. GORGÉE de nouveau fait ce assez simple.

Nous avons d'abord besoin de demander GORGÉE d'exposer son exécution dans un headerfile pour nous. Nous faisons cela avec un appel supplémentaire à RASADE:

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

Ensuite, nous avons besoin de re-compiler nos RASADE module, donnant explicitement la table des types de SWIG sait à propos d'un nom afin que nous puissions le regarder de l'intérieur de notre main.cc. Nous recompiler le .donc, en utilisant:

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

Ensuite, nous ajoutons une fonction d'aide pour la conversion de l' PyObject* de myif* dans notre 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);
}

Maintenant, c'est en place, on peut l'utiliser à partir d' 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;
}

Enfin, nous avons pour compiler main.cc avec -DSWIG_TYPE_TABLE=myif , ce qui donne:

./principal
11

12voto

eudoxos Points 4072

Exemple Minimal; à noter qu'il est compliqué par le fait qu' Base n'est pas virtuelle pure. Allons-y:

  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`
    

Et le résultat est:

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

où vous pouvez voir comment fooBase() (non-virtuel fonction c++) appels virtuel foo(), qui décide de l'ignorer, peu importe si en c++ ou python. Vous pourrait dériver une classe à partir de la Base en c++ et cela fonctionne de la même façon.

EDIT (de l'extraction d'objets 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))();

Construire py::object de PyObject* et l'utilisation py::extract à la requête si le python objet correspond à ce que vous essayez de l'extrait: PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();

10voto

Johan Lundberg Points 5835

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

"Coup de pouce.Python permet aussi de représenter C++ les relations d'héritage, de sorte que enveloppés par les classes dérivées peuvent être passées où les valeurs, les pointeurs ou des références à une classe de base sont attendus comme arguments."

Il y a des exemples de fonctions virtuelles, de sorte que résout la première partie (celle avec la classe MyCl(myif))

Pour des exemples spécifiques faisant cela, http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions

Pour la ligne myif c = MyCl(); vous devez exposer votre python (module) à C++. Il y a des exemples ici http://wiki.python.org/moin/boost.python/EmbeddingPython

8voto

Flexo Points 39273

Basé sur le (très utile) réponse par Eudoxos , j'ai pris son code et étendu tel qu'il est maintenant intégré interprète, avec un module intégré.

Cette réponse est le coup de pouce.Python équivalent de ma GORGÉE de réponse.

Le headerfile myif.h:

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

Est fondamentalement comme dans la question, mais avec une implémentation par défaut de myfunc et un destructeur virtuel.

Pour le Python de la mise en œuvre, MyCl.py j'ai en gros le même que la question:

import myif

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

Alors laisse mymain.cc, dont la plupart est basée sur la réponse 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 partie de la clé que j'ai ajouté ici, au-dessus et au-delà du "comment puis-je intégrer Python d'utilisation de Boost.Python?" et "comment puis-je prolonger Python d'utilisation de Boost.python?" (ce qui a été répondu par Eudoxos) est la réponse à la question "Comment dois-je faire les deux à la fois dans le même programme?". La solution réside dans l' PyImport_AppendInittab d'appel, qui prend la fonction d'initialisation qui devraient normalement être appelée lorsque le module est bien chargé et l'enregistre comme un module intégré. Ainsi, lorsque l'mycl.py dit - import myif il finit par importer le haut-Boost.Module Python.

1voto

pyroscope Points 2545

Jetez un œil à Boost Python, qui est l'outil le plus polyvalent et le plus puissant pour faire le pont entre C ++ et Python.

http://www.boost.org/doc/libs/1_48_0/libs/python/doc/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X