Comment puis-je implémenter une Classe C++ en Python, pour être appelé par C++?

j'ai une interface de classe écrite en C++. J'ai quelques classes qui implémentent cette interface également écrite en C++. Ceux-ci sont appelés dans le contexte d'un plus grand programme C++, qui implémente essentiellement "main". Je veux être capable d'écrire des implémentations de cette interface en Python, et les autoriser à être utilisées dans le contexte du plus grand programme C++, comme si elles avaient été simplement écrites en C++.

il y a eu beaucoup d'écrits sur l'interfaçage python et C++ mais je ne peux pas tout à fait comprendre comment faire ce que je veux. Le plus proche que je peux trouver est 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 juste.

pour être plus concret, supposons que j'ai une interface c++ existante définie quelque chose comme:

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

ce que je veux pouvoir faire c'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 pouvoir 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 ;)

36
demandé sur Flexo 2012-01-28 02:03:02

6 réponses

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++ (main comment appeler Python.


Exposant l'interface existante pour Python:

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

// 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 examiner le problème sans inclure Python dans votre application, c'est-à-dire que vous démarrez Excision en Python, pas en int main() en C++. Il est assez simple d'ajouter que plus tard.

la Première est l'obtention de croix-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é la fonctionnalité directeur de SWIG à l'échelle mondiale et spécifiquement pour notre interface. Le reste est assez standard SWIG bien.

j'ai écrit un test d'implémentation de Python:

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.i 
g++ -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7

python mycl.py 
200.0
10

exactement ce que vous espérez voir de ce test.


Intégration de Python dans l'application:

Next up nous avons besoin de mettre en place une version réelle de votre mymain.cc. J'ai fait un croquis de ce à quoi ça 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;
}

c'est essentiellement juste standard 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 main -lpython2.7
./main
200.0
10
10

la dernière pièce du puzzle est d'être capable de convertir le PyObject* que vous obtenez à partir de la création de l'instance en Python dans un myif *. SWIG rend cela à nouveau assez simple.

nous devons D'abord demander à SWIG d'exposer son runtime dans un headerfile pour nous. Nous faisons cela avec un appel supplémentaire à SWIG:

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

ensuite, nous avons besoin de recompiler notre module SWIG, en donnant explicitement la table des types que SWIG connaît sur un nom afin que nous puissions le chercher à l'intérieur de notre main.cc. We recompile the .so using:

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

ensuite nous ajoutons une fonction helper pour convertir le PyObject*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 que ceci est en place, Nous pouvons l'utiliser de l'intérieur 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 devons compiler main.cc avec -DSWIG_TYPE_TABLE=myif ce qui donne:

./main
11
39
répondu Flexo 2014-04-18 12:46:38

exemple Minimal; notez que c'est compliqué par le fait que Base n'est pas purement virtuel. Nous y voilà:

  1. baz.rpc:

    #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() (la fonction C++ non-virtuelle) appelle virtual foo(), qui se résout à la commande de surpassement indépendamment que ce soit en c++ ou en python. Vous pourriez dériver une classe de la Base en c++ et cela fonctionnerait tout de même.

EDIT (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::objectPyObject* et utiliser py::extract pour savoir si l'objet python correspond à ce que vous essayez d'extraire:PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();

12
répondu eudoxos 2012-01-31 13:13:50

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

"coup de pouce.Python nous permet également de représenter les relations d'héritage C++ de sorte que les classes dérivées enveloppées peuvent être passées où des valeurs, des 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 de faire ce, 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

10
répondu Johan Lundberg 2012-01-27 22:45:56

Basé sur la (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, c'est le coup de pouce.Équivalent Python de ma GORGÉE de réponse.

le fichier principal 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 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

il reste alors mymain.cc, dont la plupart est basée sur la réponse D'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 clé que j'ai ajoutée ici, au-delà du "comment intégrer Python en utilisant Boost.Python?"et" comment puis-je étendre Python en utilisant Boost.python?"(ce qui a été répondu par Eudoxos) est la réponse à la question "Comment puis-je faire les deux à la fois dans le même programme?". La solution réside dans l' PyImport_AppendInittab 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 mycl.py says import myif il finit par importer le Boost intégré.Module Python.

8
répondu Flexo 2017-05-23 12:25:34

jetez un coup d'oeil à 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/

1
répondu pyroscope 2012-01-27 22:35:34

il n'y a pas de vrai moyen d'interfacer le code C++ directement avec Python.

SWIG gère cela, mais il construit son propre wrapper.

une alternative que je préfère à SWIG est ctypes, mais pour l'utiliser, vous devez créer un C wrapper.

Pour l'exemple:

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

créer un wrapper C comme ceci:

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

puisque vous construisez en utilisant C++, l'externe " C " permet le couplage C de sorte que vous pouvez l'appeler facilement à partir de votre dll, et __declspec (dllexport) permet à la fonction d'être appelée de 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)

alors que SWIG fait tout cela pour vous, il y a peu de place pour vous de modifier les choses comme vous le souhaitez sans être frustré par tous les changements que vous avez à refaire lorsque vous régénérez L'emballage de SWIG.

un problème avec les ctypes est qu'il ne gère pas les structures STL, car il est fait pour C. SWIG ne gère pas cela pour vous, mais vous pouvez être en mesure de l'envelopper vous-même dans le C. C'est à vous.

voici le python doc pour les ctypes:

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

en outre, la dll construite devrait être dans le même dossier que votre interface Python (pourquoi ne le serait-elle pas?).

je suis curieux cependant, pourquoi voudriez-vous appeler Python de L'intérieur de C++ au lieu d'appeler directement L'implémentation C++?

-2
répondu Pochi 2012-01-30 18:59:14