web-dev-qa-db-fra.com

Passer un objet C ++ à Python

Cette question concerne comment passer un objet C++ à une fonction python qui est appelée dans un (C++) incorporé Python.

La classe C++ suivante (MyClass.h) est conçue pour les tests:

#ifndef MyClassH
#define MyClassH
#include <string>

using std::string;
class MyClass
{
    public:
                        MyClass(const string& lbl): label(lbl) {}
                        ~MyClass(){}
        string          getLabel() {return label;}

    private:
        string          label;
};
#endif

Un module python, exposant la classe C++, peut être généré par le fichier d'interface Swig suivant:

%module passmetopython

%{    #include "MyClass.h"    %}

%include "std_string.i"

//Expose to Python
%include "MyClass.h"

Voici un script Python utilisant le module python

import passmetopython as pmtp

def execute(obj):
    #This function is to be called from C/C++, with a
    #MyClass object as an argument
    print ("Entering execute function")
    lbl = obj.getLabel();
    print ("Printing from within python execute function. Object label is: " + lbl)
    return True

def main():
    c = pmtp.MyClass("Test 1")
    retValue = execute(c)
    print("Return value: " + str(retValue))

#Test function from within python
if __name__ == '__main__':
    main()

Cette question concerne la façon de faire fonctionner la fonction python execute (), lorsqu'elle est appelée à partir de c ++, avec un objet C++ comme argument.

Le programme C++ suivant a été écrit pour tester les fonctions (quantité minimale de vérification des erreurs):

#include "Python.h"
#include <iostream>
#include <sstream>
#include "MyClass.h"

using namespace std;

int main()
{
    MyClass obj("In C++");
    cout << "Object label: \"" << obj.getLabel() << "\"" << endl;

    //Setup the Python interpreter and eventually call the execute function in the
    //demo python script
    Py_Initialize();

    //Load python Demo script, "passmetopythonDemo.py"
    string PyModule("passmetopythonDemo");
    PyObject* pm = PyUnicode_DecodeFSDefault(PyModule.c_str());

    PyRun_SimpleString("import sys");
    stringstream cmd;
    cmd << "sys.path.append(\"" << "." << "\")";
    PyRun_SimpleString(cmd.str().c_str());
    PyObject* PyModuleP = PyImport_Import(pm);
    Py_DECREF(pm);

    //Now create PyObjects for the Python functions that we want to call
    PyObject* pFunc = PyObject_GetAttrString(PyModuleP, "execute");

    if(pFunc)
    {
        //Setup argument
        PyObject* pArgs = PyTuple_New(1);

        //Construct a PyObject* from long
        PyObject* pObj(NULL);

        /* My current attempt to create avalid argument to Python */
        pObj = PyLong_FromLong((long) &obj);


        PyTuple_SetItem(pArgs, 0, pObj);

        /***** Calling python here *****/
        cout<<endl<<"Calling function with an MyClass argument\n\n";
        PyObject* res = PyObject_CallObject(pFunc, pArgs);
        if(!res)
        {
            cerr << "Failed calling function..";
        }
    }

    return 0;
}

Lors de l'exécution du code ci-dessus, la fonction execute () python, avec un objet MyClass comme argument, échoue et renvoie NULL. Cependant, la fonction Python est entrée, car je peux voir la sortie ( Saisie de la fonction d'exécution) dans la sortie de la console, indiquant que l'objet est passé n'est pas, en effet , un objet MyClass valide.

Il y a beaucoup d'exemples sur la façon de passer des types simples, comme des entiers, des doubles ou des types de chaîne à Python de C/C++. Mais il y a très peu d'exemples montrant comment passer un C/C++ objet/pointeur, ce qui est un peu déroutant.

Le code ci-dessus, avec un fichier CMake, peut être extrait de github: https://github.com/TotteKarlsson/miniprojects/tree/master/passMeToPython

Ce code ne doit pas utiliser de boost python ou d'autres API. Cython semble intéressant cependant, et s'il peut être utilisé pour simplifier du côté C++, il pourrait être acceptable.

28
Totte Karlsson

Ceci est une réponse partielle à ma propre question. Je dis partial, parce que je crois qu'il y a une meilleure façon.

S'appuyant sur ce post http://swig.10945.n7.nabble.com/Pass-a-Swig-wrapped-C-class-to-embedded-Python-code-td8812.html J'ai généré l'en-tête d'exécution swig, comme décrit ici, section 15.4: http://www.swig.org/Doc2.0/Modules.html#Modules_external_run_time

En incluant l'en-tête généré dans le code C++ ci-dessus, autorisez l'écriture du code suivant:

    PyObject* pObj = SWIG_NewPointerObj((void*)&obj, SWIG_TypeQuery("_p_MyClass"),  0 ); 

Ce code utilise les informations des fichiers source de swig python wrap, à savoir le nom "swig" du type MyClass, c'est-à-dire _ p_MyClass.

Avec le PyObject * ci-dessus comme argument de la fonction PyObject_CallObject, la fonction python execute () dans le code ci-dessus s'exécute correctement, et le code Python, en utilisant le module python généré), a un accès approprié aux données internes des objets MyClass. C'est très bien.

Bien que le code ci-dessus illustre comment passer et récupérer des données entre C++ et Python d'une manière assez simple, ce n'est pas idéal, à mon avis.

L'utilisation du fichier d'en-tête swig dans le code C++ n'est vraiment pas si jolie, et en outre, il nécessite qu'un utilisateur examine "manuellement" le code wrapper généré par swig afin de trouver le code "_p_MyClass".

Il doit y avoir une meilleure façon!? Peut-être que quelque chose devrait être ajouté au fichier d'interface swig afin de rendre cela plus agréable?

8
Totte Karlsson