c++ - resultado - Método de Python para impulsar la función
guardar funciones en python (1)
Tengo un método exportado a Python usando boost python que toma una función boost :: como argumento.
Por lo que he leído boost :: python debería soportar boost :: function sin mucho alboroto, pero cuando trato de llamar a la función con un método de python me da este error
Boost.Python.ArgumentError: Python argument types in
Class.createTimer(Class, int, method, bool)
did not match C++ signature:
createTimer(class Class {lvalue}, unsigned long interval,
class boost::function<bool _cdecl(void)> function, bool recurring=False)
Lo estoy llamando desde Python con este código
self.__class.createTimer( 3, test.timerFunc, False )
y en C ++ se define como
boost::int32_t createTimer( boost::uint32_t interval, boost::function< bool() > function, bool recurring = false );
El objetivo aquí es una clase de temporizador donde puedo hacer algo como
class->createTimer( 3, boost::bind( &funcWithArgs, arg1, arg2 ) )
para crear un temporizador que ejecuta el funcWithArgs. Gracias a boost bind esto funcionará con casi cualquier función o método.
Entonces, ¿cuál es la sintaxis que necesito usar para boost :: python para aceptar mis funciones de python como una función boost ::?
Obtuve una respuesta en la lista de correo de Python, y después de un poco de reelaboración y más investigación conseguí exactamente lo que quería :)
Vi esa publicación antes que mithrandi, pero no me gustó la idea de tener que declarar las funciones de esa manera. ¡Con algunas envolturas elegantes y un poco de magia de pitón esto puede funcionar y verse bien al mismo tiempo!
Para comenzar, termine su objeto python con un código como este
struct timer_func_wrapper_t
{
timer_func_wrapper_t( bp::object callable ) : _callable( callable ) {}
bool operator()()
{
// These GIL calls make it thread safe, may or may not be needed depending on your use case
PyGILState_STATE gstate = PyGILState_Ensure();
bool ret = _callable();
PyGILState_Release( gstate );
return ret;
}
bp::object _callable;
};
boost::int32_t createTimerWrapper( Class* class, boost::uint64_t interval, bp::object function, bool recurring = false )
{
return class->createTimer( interval, boost::function<bool ()>( timer_func_wrapper_t( function ) ), recurring );
}
cuando en tu clase defines el método como tal
.def( "createTimer", &createTimerWrapper, ( bp::arg( "interval" ), bp::arg( "function" ), bp::arg( "recurring" ) = false ) )
Con ese pedacito de envoltura puedes hacer magia como esta
import MyLib
import time
def callMePls():
print( "Hello world" )
return True
class = MyLib.Class()
class.createTimer( 3, callMePls )
time.sleep( 1 )
Para imitar completamente el C ++, también necesitamos una implementación boost :: bind que se puede encontrar aquí: http://code.activestate.com/recipes/440557/
Con eso, ahora podemos hacer algo como esto
import MyLib
import time
def callMePls( str ):
print( "Hello", str )
return True
class = MyLib.Class()
class.createTimer( 3, bind( callMePls, "world" ) )
time.sleep( 1 )
EDITAR:
Me gusta seguir mis preguntas cuando puedo. Estuve usando este código exitosamente por un tiempo, pero descubrí que esto se desmorona cuando quieres tomar la función boost :: en constructores de objetos. Hay una manera de hacerlo funcionar de manera similar a esto, pero el nuevo objeto que construyes termina con una firma diferente y no funcionará con otros objetos como él.
Esto finalmente me molestó lo suficiente como para hacer algo al respecto y dado que sé más sobre boost :: python, ahora se me ocurrió una solución bastante buena para todos los convertidores. Este código aquí convertirá un python invocable en un objeto boost :: python <bool ()>, se puede modificar fácilmente para convertirlo a otras funciones de impulso.
// Wrapper for timer function parameter
struct timer_func_wrapper_t
{
timer_func_wrapper_t( bp::object callable ) : _callable(callable) {}
bool operator()()
{
return _callable();
}
bp::object _callable;
};
struct BoostFunc_from_Python_Callable
{
BoostFunc_from_Python_Callable()
{
bp::converter::registry::push_back( &convertible, &construct, bp::type_id< boost::function< bool() > >() );
}
static void* convertible( PyObject* obj_ptr )
{
if( !PyCallable_Check( obj_ptr ) ) return 0;
return obj_ptr;
}
static void construct( PyObject* obj_ptr, bp::converter::rvalue_from_python_stage1_data* data )
{
bp::object callable( bp::handle<>( bp::borrowed( obj_ptr ) ) );
void* storage = ( ( bp::converter::rvalue_from_python_storage< boost::function< bool() > >* ) data )->storage.bytes;
new (storage)boost::function< bool() >( timer_func_wrapper_t( callable ) );
data->convertible = storage;
}
};
Luego, en tu código de inicio, es decir, BOOST_PYTHON_MODULE (), simplemente registra el tipo creando la estructura
BOOST_PYTHON_MODULE(Foo)
{
// Register function converter
BoostFunc_from_Python_Callable();