¿Cómo se propagan las excepciones de C ++ a Python en una biblioteca de envoltura SWIG?

Estoy escribiendo un envoltorio SWIG alrededor de una biblioteca de C ++ personalizada que define sus propios tipos de excepción de C ++. Los tipos de excepción de la biblioteca son más ricos y más específicos que las excepciones estándar. (Por ejemplo, una clase representa errores de análisis y tiene una colección de números de línea). ¿Cómo puedo propagar esas excepciones a Python mientras se conserva el tipo de excepción?

Sé que esta pregunta tiene algunas semanas de antigüedad, pero la encontré porque estaba buscando una solución para mí. Así que voy a intentar una respuesta, pero le advierto de antemano que puede no ser una solución atractiva, ya que los archivos de interfaz de swig pueden ser más complicados que codificar a mano la envoltura. Además, por lo que puedo decir, la documentación de swig nunca trata directamente con excepciones definidas por el usuario.

Supongamos que desea lanzar la siguiente excepción desde su módulo de código c ++, mylibrary.cpp, junto con un pequeño mensaje de error que se debe detectar en su código de Python:

throw MyException("Highly irregular condition..."); /* C++ code */ 

MyException es una excepción de usuario definida en otro lugar.

Antes de comenzar, tome nota de cómo debería capturarse esta excepción en su python. Para nuestros propósitos aquí, digamos que tiene un código de Python como el siguiente:

 import mylibrary try: s = mylibrary.do_something('foobar') except mylibrary.MyException, e: print(e) 

Creo que esto describe tu problema.

Mi solución implica hacer cuatro adiciones a su archivo de interfaz de swig (mylibrary.i) de la siguiente manera:

Paso 1: en la directiva de encabezado (el bloque% {…%} generalmente sin nombre) agregue una statement para el puntero a la excepción que reconoce Python, que llamaremos pMyException. El paso 2 a continuación definirá esto:

 %{ #define SWIG_FILE_WITH_INIT /* for eg */ extern char* do_something(char*); /* or #include "mylibrary.h" etc */ static PyObject* pMyException; /* add this! */ %} 

Paso 2: agregue una directiva de inicialización (la “m” es particularmente atroz, pero eso es lo que el swig v1.3.40 necesita actualmente en ese momento en su archivo contenedor construido) – pMyException se declaró en el paso 1 anterior:

 %init %{ pMyException = PyErr_NewException("_mylibrary.MyException", NULL, NULL); Py_INCREF(pMyException); PyModule_AddObject(m, "MyException", pMyException); %} 

Paso 3: Como se mencionó en una publicación anterior, necesitamos una directiva de excepción: la nota “% excepto (python)” está en desuso. Esto envolverá la función c +++ “do_something” en un bloque try-except que captura la excepción de c ++ y la convierte en la excepción de python definida en el paso 2 anterior:

 %exception do_something { try { $action } catch (MyException &e) { PyErr_SetString(pMyException, const_cast(e.what())); SWIG_fail; } } /* The usual functions to be wrapped are listed here: */ extern char* do_something(char*); 

Paso 4: Debido a que swig configura un envoltorio de python (un módulo ‘sombra’) alrededor de la dll .pyd, también debemos asegurarnos de que nuestro código de python pueda “ver a través” del archivo .pyd. Lo siguiente funcionó para mí y es preferible editar el código del swig py wrapper directamente:

 %pythoncode %{ MyException = _mylibrary.MyException %} 

Probablemente sea demasiado tarde para ser de mucha utilidad para el póster original, pero tal vez alguien más encuentre las sugerencias anteriores de algún uso. Para trabajos pequeños, puede preferir la limpieza de un envoltorio de extensión c ++ codificado a mano a la confusión de un archivo de interfaz swig.


Adicional:

En mi lista de archivos de interfaz anterior, omití la directiva del módulo estándar porque solo quería describir las adiciones necesarias para hacer que las excepciones funcionen. Pero tu línea de módulo debería verse así:

 %module mylibrary 

Además, su setup.py (si está utilizando distutils, que recomiendo al menos para comenzar) debe tener un código similar al siguiente, de lo contrario, el paso 4 fallará cuando no se reconozca _mylibrary:

 /* setup.py: */ from distutils.core import setup, Extension mylibrary_module = Extension('_mylibrary', extra_compile_args = ['/EHsc'], sources=['mylibrary_wrap.cpp', 'mylibrary.cpp'],) setup(name="mylibrary", version="1.0", description='Testing user defined exceptions...', ext_modules=[mylibrary_module], py_modules = ["mylibrary"],) 

Tenga en cuenta el indicador de comstackción / EHsc, que necesitaba en Windows para comstackr excepciones. Tu plataforma puede no requerir esa bandera; Google tiene los detalles.

He probado el código con mis propios nombres, que he convertido aquí en “mylibrary” y “MyException” para ayudar a generalizar la solución. Esperemos que los errores de transcripción sean pocos o ninguno. Los puntos principales son las directivas% init y% pythoncode junto con

 static PyObject* pMyException; 

en la directiva de cabecera.

Espero que se aclare la solución.

Agregaré un poco aquí, ya que el ejemplo dado aquí ahora dice que “% except (python)” está en desuso …

Ahora puede (a partir de swig 1.3.40, de todos modos) hacer una traducción totalmente genérica, independiente del lenguaje de script. Mi ejemplo sería:

 %exception { try { $action } catch (myException &e) { std::string s("myModule error: "), s2(e.what()); s = s + s2; SWIG_exception(SWIG_RuntimeError, s.c_str()); } catch (myOtherException &e) { std::string s("otherModule error: "), s2(e.what()); s = s + s2; SWIG_exception(SWIG_RuntimeError, s.c_str()); } catch (...) { SWIG_exception(SWIG_RuntimeError, "unknown exception"); } } 

Esto generará una excepción RuntimeError en cualquier lenguaje de secuencias de comandos compatible, incluido Python, sin obtener elementos específicos de Python en sus otros encabezados.

Debe poner esto antes de las llamadas que quieren que esta excepción se maneje.

De la documentación del gulp.

 %except(python) { try { $function } catch (RangeError) { PyErr_SetString(PyExc_IndexError,"index out-of-bounds"); return NULL; } } 

¿Es la documentación de la excepción del gulp alguna ayuda? Menciona la definición de diferentes manejadores de excepciones .

U también puede usar:

capturas: http://www.swig.org/Doc3.0/SWIGPlus.html#SWIGPlus_catches

Ejemplo:

 %catches(std::exception, std::string, int, ...); 

que genera para cada función un bloque try catch:

  try { result = (namespace::Function *)new namespace::Function ((uint16_t const *)arg1); } catch(std::exception &_e) { SWIG_exception_fail(SWIG_SystemError, (&_e)->what()); } catch(std::string &_e) { SWIG_Python_Raise(SWIG_From_std_string(static_cast< std::string >(_e)), "std::string", 0); SWIG_fail; } catch(int &_e) { SWIG_Python_Raise(SWIG_From_int(static_cast< int >(_e)), "int", 0); SWIG_fail; } catch(...) { SWIG_exception_fail(SWIG_RuntimeError,"unknown exception"); }