2012-12-05 14 views
5

Pracuję więc nad małym projektem, w którym używam Pythona jako osadzonego silnika skryptów. Do tej pory nie miałem z tym problemu, używając boost.python, ale jest coś, co chciałbym z nim zrobić, jeśli to możliwe.Boost.Python - Przekazywanie boost :: python :: object jako argument do funkcji Pythona?

Zasadniczo, Python może być użyty do rozszerzenia moich klas C++ poprzez dodanie funkcji, a nawet wartości danych do klasy. Chciałbym móc je utrzymywać w C++, więc jedna funkcja Pythona może dodawać elementy danych do klasy, a później ta sama instancja przekazana do innej funkcji nadal będzie je mieć. Celem jest napisanie ogólnego silnika rdzeniowego w C++ i umożliwienie użytkownikom rozszerzenia go w Pythonie w dowolny sposób, bez konieczności dotykania C++.

Więc co myślałem, że działa to, że chciałbym przechowywać boost::python::object w klasie C++ jako wartość self i przy wywołaniu Pythona z C++, chciałbym wysłać ten obiekt Pythona przez boost::python::ptr(), tak że modyfikacje w strona Pythona pozostałaby z powrotem w klasie C++. Niestety, gdy próbuję to, pojawia się następujący błąd:

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

Czy istnieje jakiś sposób przekazywania obiektu bezpośrednio do funkcji Pythona tak, albo jakiś inny sposób mogę iść o to, aby osiągnąć moje życzenia wynik?

Z góry dziękuję za pomoc. :)

+0

Znaleziono jedno rozwiązanie.W pythonie dodałem funkcję: def ProcessEvent (event, obj): return globals() [event] (obj.PyObj) W C++ dodałem 'boost :: python :: object' o nazwie 'PyObj' do mojej klasy C++, która jest inicjowana do' boost :: python :: ptr (this) 'i wywołała moje zdarzenia z tym, przekazując nazwę zdarzenia, które chcę nazwać jako pierwszy parametr i' boost :: python :: ptr' do obiektu, który chcę przekazać jako drugi. Działa to tak, jak się spodziewałem - kiedy dodałem atrybut w jednym zdarzeniu, wciąż tam był, kiedy przekazywałem go innemu. Może nie najlepsze rozwiązanie ... Ale działa. –

Odpowiedz

5

Dostałam to fantastyczne rozwiązanie z listy wysyłkowej C++ sig.

Zaimplementuj std::map<std::string, boost::python::object> w klasie C++, następnie przeładuj __getattr__() i __setattr__(), aby odczytać i zapisać na tym std :: map. Następnie po prostu wyślij go do Pythona z boost::python::ptr() jak zwykle, nie musisz przechowywać obiektu po stronie C++ lub wysłać go do Pythona. Działa idealnie.

Edycja: Zauważyłem również, że musiałem nadpisać funkcję __setattr__() w specjalny sposób, ponieważ było to zrywanie rzeczy dodanych z add_property(). Te rzeczy działały poprawnie podczas ich pobierania, ponieważ pyton sprawdza atrybuty klasy przed wywołaniem __getattr__(), ale nie ma takiej kontroli z __setattr__(). Po prostu nazywa to bezpośrednio. Musiałem więc wprowadzić pewne zmiany, aby zmienić to w pełne rozwiązanie. Oto pełne wdrożenie rozwiązania:

najpierw utworzyć zmienną globalną:

boost::python::object PyMyModule_global; 

utworzyć klasę następująco (z tym, co inne informacje, które chcesz dodać do niego):

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

Następnie zdefiniuj moduł python w następujący sposób, dodając wszelkie inne defy i właściwości, które chcesz:

Następnie zainicjowania Pythona:

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

Po wykonaniu tego wszystkiego, będziesz w stanie utrzymywać się zmiany dokonane w instancji pytona nad do C++. Cokolwiek zdefiniowane w C++ jako atrybut zostanie poprawnie obsłużone, a wszystko, czego nie ma, zostanie dodane do dict zamiast do klasy __dict__.

+0

Świetnie! Dzięki za udostępnienie! – mrmclovin

Powiązane problemy