2012-12-05 6 views
5

Also arbeite ich an einem kleinen Projekt, in dem ich Python als eingebettete Skript-Engine verwende. Bisher habe ich mit boost.python nicht viel Mühe damit gehabt, aber es gibt etwas, das ich gerne machen würde, wenn es möglich ist.Boost.Python - Übergabe von boost :: python :: object als Argument an die Python-Funktion?

Grundsätzlich kann Python verwendet werden, um meine C++ - Klassen durch Hinzufügen von Funktionen und sogar Datenwerte zur Klasse zu erweitern. Ich möchte in der Lage sein, diese auf der C++ Seite beibehalten zu haben, so dass eine Python-Funktion Datenmitglieder zu einer Klasse hinzufügen kann, und später dieselbe Instanz, die an eine andere Funktion weitergegeben wird, hat sie immer noch. Das Ziel besteht darin, eine generische Core-Engine in C++ zu schreiben und die Benutzer in Python beliebig zu erweitern, ohne dass sie jemals C++ berühren müssen.

Also, was ich dachte, dass war arbeiten, dass ich ein boost::python::object in der C++ Klasse als Wert speichern würde self, und wenn der Python aus dem C++ aufrufen, würde ich das Python-Objekt durch boost::python::ptr(), so dass Änderungen an dem schicken Python-Seite würde in der C++ - Klasse beibehalten. Leider, wenn ich das versuche, erhalte ich folgende Fehlermeldung:

TypeError: No to_python (by-value) converter found for C++ type: boost::python::api::object

Gibt es eine Möglichkeit, ein Objekt direkt mit einer Python-Funktion wie die Weitergabe oder jede andere Art, wie ich zu diesem gehen kann mein Wunsch zu erreichen Ergebnis?

Vielen Dank im Voraus für jede Hilfe. :)

+0

Eine Lösung gefunden.In dem Python, habe ich eine Funktion: def Process (event, obj): Globals zurückkehren() [Veranstaltung] (obj.PyObj) In der C++, habe ich einen 'boost :: Python :: object' benannte 'PyObj' zu meiner C++ - Klasse, die zu' boost :: python :: ptr (this) 'initialisiert wurde, und rief meine Ereignisse mit diesem auf, indem ich den Namen des Ereignisses, das ich aufrufen möchte, als ersten Parameter und ein' boost :: python :: ptr' an das Objekt, das ich als zweites übergeben möchte. Das funktioniert, wie ich gehofft hatte - als ich ein Attribut in einem Ereignis hinzugefügt habe, war es immer noch da, als ich es an ein anderes weitergegeben habe. Vielleicht nicht die beste Lösung ... Aber es funktioniert. –

Antwort

5

Haben Sie diese fantastische Lösung aus der C++ Sig Mailing-Liste.

Implementieren Sie eine std::map<std::string, boost::python::object> in der C++ - Klasse, dann überlasten Sie __getattr__() und __setattr__(), um von dieser std :: map zu lesen und zu schreiben. Dann senden Sie es einfach wie gewohnt an die Python mit boost::python::ptr(), keine Notwendigkeit, ein Objekt auf der C++ - Seite zu halten oder an die Python zu senden. Es funktioniert perfekt.

Edit: Ich fand auch, dass ich die __setattr__() Funktion in einer speziellen Weise überschreiben musste, da es Sachen brach, die ich mit add_property() hinzufügte. Diese Dinge funktionierten gut, als sie erhalten, da Python überprüft die Attribute einer Klasse vor dem Aufruf __getattr__(), aber es gibt keine solche Prüfung mit __setattr__(). Es ruft es einfach direkt an. Also musste ich einige Änderungen vornehmen, um daraus eine vollständige Lösung zu machen. Hier ist die vollständige Implementierung der Lösung:

Zuerst eine globale Variable erstellen:

boost::python::object PyMyModule_global; 

Erstellen Sie eine Klasse wie folgt (mit dem, was andere Informationen, die Sie es hinzufügen möchten):

class MyClass 
{ 
public: 
    //Python checks the class attributes before it calls __getattr__ so we don't have to do anything special here. 
    boost::python::object Py_GetAttr(std::string str) 
    { 
     if(dict.find(str) == dict.end()) 
     { 
     PyErr_SetString(PyExc_AttributeError, JFormat::format("MyClass instance has no attribute '{0}'", str).c_str()); 
     throw boost::python::error_already_set(); 
     } 
     return dict[str]; 
    } 

    //However, with __setattr__, python doesn't do anything with the class attributes first, it just calls __setattr__. 
    //Which means anything that's been defined as a class attribute won't be modified here - including things set with 
    //add_property(), def_readwrite(), etc. 
    void Py_SetAttr(std::string str, boost::python::object val) 
    { 
     try 
     { 
     //First we check to see if the class has an attribute by this name. 
     boost::python::object obj = PyMyModule_global["MyClass"].attr(str.c_str()); 
     //If so, we call the old cached __setattr__ function. 
     PyMyModule_global["MyClass"].attr("__setattr_old__")(ptr(this), str, val); 
     } 
     catch(boost::python::error_already_set &e) 
     { 
     //If it threw an exception, that means that there is no such attribute. 
     //Put it on the persistent dict. 
     PyErr_Clear(); 
     dict[str] = val; 
     } 
    } 
private: 
    std::map<std::string, boost::python::object> dict; 
}; 

Definieren Sie dann das Python-Modul wie folgt und fügen Sie die gewünschten anderen Definitionen und Eigenschaften hinzu:

BOOST_PYTHON_MODULE(MyModule) 
{ 
    boost::python::class_<MyClass>("MyClass", boost::python::no_init) 
     .def("__getattr__", &MyClass::Py_GetAttr) 
     .def("__setattr_new__", &MyClass::Py_SetAttr); 
} 

Dann initialisieren Python:

void PyInit() 
{ 
    //Initialize module 
    PyImport_AppendInittab("MyModule", &initMyModule); 
    //Initialize Python 
    Py_Initialize(); 

    //Grab __main__ and its globals 
    boost::python::object main = boost::python::import("__main__"); 
    boost::python::object global = main.attr("__dict__"); 

    //Import the module and grab its globals 
    boost::python::object PyMyModule = boost::python::import("MyModule"); 
    global["MyModule"] = PyMyModule; 
    PyMyModule_global = PyMyModule.attr("__dict__"); 

    //Overload MyClass's setattr, so that it will work with already defined attributes while persisting new ones 
    PyMyModule_global["MyClass"].attr("__setattr_old__") = PyMyModule_global["MyClass"].attr("__setattr__"); 
    PyMyModule_global["MyClass"].attr("__setattr__") = PyMyModule_global["MyClass"].attr("__setattr_new__"); 
} 

Nachdem Sie all dies getan haben, werden Sie in der Lage sein, Änderungen an der in Python über auf die C gemacht Instanz anhalten ++. Alles, was in C++ als Attribut definiert ist, wird korrekt behandelt, und alles, was nicht in C++ enthalten ist, wird an dict anstatt an die Klasse __dict__ angehängt.

+0

Großartig! Danke für das Teilen! – mrmclovin

Verwandte Themen