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.
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. –