2012-12-05 17 views
5

Quindi sto lavorando ad un piccolo progetto in cui sto usando Python come motore di scripting incorporato. Finora non ho avuto grossi problemi con l'utilizzo di boost.python, ma c'è qualcosa che mi piacerebbe fare se fosse possibile.Boost.Python - Passa boost :: python :: object come argomento alla funzione python?

Fondamentalmente, Python può essere utilizzato per estendere le mie classi C++ aggiungendo funzioni e anche valori di dati alla classe. Mi piacerebbe poter avere questi persistono nel lato C++, quindi una funzione python può aggiungere membri di dati a una classe, e in seguito la stessa istanza passata a una funzione diversa li avrà comunque. L'obiettivo è scrivere un motore di base generico in C++ e consentire agli utenti di estenderlo in Python in qualsiasi modo necessario senza dover mai toccare il C++.

Quindi quello che pensavo sarebbe opera è stata che avrei memorizzare un boost::python::object nella classe C++ come valore self, e quando si chiama il pitone dal C++, manderei quell'oggetto pitone attraverso boost::python::ptr(), in modo che le modifiche sul il lato python tornerebbe alla classe C++. Purtroppo quando provo questo, ottengo il seguente errore:

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

C'è un modo di passare un oggetto direttamente a una funzione pitone del genere, o in qualsiasi altro modo che posso andare su questo per raggiungere il mio desiderare risultato?

Grazie in anticipo per qualsiasi aiuto. :)

+0

Trovato una soluzione.In pitone, ho aggiunto una funzione: def processEvent (evento, obj): tornare globals() [evento] (obj.PyObj) In C++, ho aggiunto un boost :: :: pitone object' ' chiamato 'PyObj' nella mia classe C++, che è inizializzato in' boost :: python :: ptr (this) ', e chiamato i miei eventi con quello, passando il nome dell'evento che voglio chiamare come primo parametro e un' boost :: python :: ptr' all'oggetto che voglio passare ad esso come secondo. Funziona come speravo - quando ho aggiunto un attributo in un evento, era ancora lì quando l'ho passato ad un altro. Forse non è la soluzione migliore ... Ma funziona. –

risposta

5

Hai ottenuto questa fantastica soluzione dalla mailing list C++ sig.

Implementare un std::map<std::string, boost::python::object> nella classe C++, quindi sovraccaricare __getattr__() e __setattr__() da leggere e scrivere su quella std :: map. Quindi basta inviarlo al python con boost::python::ptr() come al solito, non c'è bisogno di tenere un oggetto sul lato C++ o mandarne uno al python. Funziona perfettamente.

Modifica: ho anche scoperto che dovevo ignorare la funzione __setattr__() in un modo speciale poiché si trattava di rompere le cose che ho aggiunto con add_property(). Queste cose hanno funzionato bene quando sono state acquisite, dal momento che Python controlla gli attributi di una classe prima di chiamare __getattr__(), ma non c'è alcun controllo con __setattr__(). Lo chiama solo direttamente. Quindi ho dovuto apportare alcune modifiche per trasformare questo in una soluzione completa. Ecco la piena attuazione della soluzione:

Innanzitutto creare una variabile globale:

boost::python::object PyMyModule_global; 

creare una classe come segue (con qualsiasi altra informazione che si desidera aggiungere ad essa):

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

Quindi definire il modulo python come segue, aggiungendo qualsiasi altro def o proprietà che si desidera:

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

Poi inizializzare pitone:

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__"); 
} 

Una volta fatto tutto questo, sarete in grado di persistere modifiche all'istanza realizzati in pitone verso il C++. Tutto ciò che è definito in C++ come un attributo verrà gestito correttamente e tutto ciò che non lo sarà verrà aggiunto a dict anziché a __dict__ della classe.

+0

Ottimo! Grazie per la condivisione! – mrmclovin

Problemi correlati