2012-05-21 14 views
6

Tengo algunos problemas para manejar excepciones personalizadas de C++ cuando llamo desde Cython. Mi situación es la siguiente: tengo una biblioteca que usa CustomLibraryException para todas las excepciones. Lo que quiero es básicamente obtener el mensaje de error y generar un error de Python con él.Manejo de excepciones de C++ personalizadas en Cython

El user guide tiene algunas pistas, pero es un poco inespecífico. La primera posibilidad es hacer:

cdef bar int() excepto + ValueError

Esto convierte la CustomLibraryException a un ValueError, pero pierde el mensaje de error.

La otra posibilidad es convertir explícitamente el error utilizando

cdef int raise_py_error() 
cdef int something_dangerous() except +raise_py_error 

realmente no understant esta solución. Entendí que raise_py_error tiene que ser una función de C++ que de alguna manera maneja el error. Aunque no estoy seguro de cómo manejarlo. La función no obtiene un argumento y se invoca dentro del bloque catch en C++.

Si alguien tiene un ejemplo práctico de manejar una excepción de C++ en Cython, eso sería de gran ayuda.

Respuesta

2

Si CustomLibraryException deriva de std::runtime_error (como debe ser una excepción de C++ con buen comportamiento), entonces el comportamiento que está viendo es un error en Cython.

Si no es así, entonces la cosa más fácil de hacer es envolver la función de C++ que está llamando en un C función auxiliar ++ que traduce la excepción:

double foo(char const *, Bla const &); // this is the one we're wrapping 

double foo_that_throws_runtime_error(char const *str, Bla const &blaref) 
{ 
    try { 
     return foo(str, blaref); 
    } catch (CustomLibraryException const &e) { 
     throw std::runtime_error(e.get_the_message()); 
    } 
} 

Esto causará una RuntimeError estar criado en el lado de Python. Alternativamente, throw un std::invalid_argument a raise a ValueError, etc. (consulte la tabla en la página que ha vinculado).

+0

La excepción no se deriva de '' std :: runtime'' como esperaba. Gracias por la ayuda :) Eso no lo hace mucho mejor, sin embargo. Las funciones que generan el error son funciones miembro y no quiero cambiar su código. Esto es sobre mis [envoltorios gco] (http://peekaboo-vision.blogspot.de/2012/05/graphcuts-for-python-pygco.html) y la licencia no me permite redistribuir: -/ –

+1

@ AndreasMueller: las funciones de los miembros se pueden envolver fácilmente en funciones independientes; simplemente pase el objeto sobre el que deberían operar como primer argumento: 'contenedor vacío (Obj & o, int ham) {return o.wrapped (ham); } ' –

+1

Sí, lo sé, era perezoso para hacerlo todavía;) Por cierto, ¿sabías que eres el mejor cartel número 100 en SO? –

3

El C por defecto ++ gestor de excepciones en Cython debe ilustrar exactamente cómo llevar a cabo lo que está tratando de hacer:

static void __Pyx_CppExn2PyErr() { 
    // Catch a handful of different errors here and turn them into the 
    // equivalent Python errors. 
    try { 
    if (PyErr_Occurred()) 
     ; // let the latest Python exn pass through and ignore the current one 
    else 
     throw; 
    } catch (const std::bad_alloc& exn) { 
    PyErr_SetString(PyExc_MemoryError, exn.what()); 
    } catch (const std::bad_cast& exn) { 
    PyErr_SetString(PyExc_TypeError, exn.what()); 
    } catch (const std::domain_error& exn) { 
    PyErr_SetString(PyExc_ValueError, exn.what()); 
    } catch (const std::invalid_argument& exn) { 
    PyErr_SetString(PyExc_ValueError, exn.what()); 
    } catch (const std::ios_base::failure& exn) { 
    // Unfortunately, in standard C++ we have no way of distinguishing EOF 
    // from other errors here; be careful with the exception mask 
    PyErr_SetString(PyExc_IOError, exn.what()); 
    } catch (const std::out_of_range& exn) { 
    // Change out_of_range to IndexError 
    PyErr_SetString(PyExc_IndexError, exn.what()); 
    } catch (const std::overflow_error& exn) { 
    PyErr_SetString(PyExc_OverflowError, exn.what()); 
    } catch (const std::range_error& exn) { 
    PyErr_SetString(PyExc_ArithmeticError, exn.what()); 
    } catch (const std::underflow_error& exn) { 
    PyErr_SetString(PyExc_ArithmeticError, exn.what()); 
    } catch (const std::exception& exn) { 
    PyErr_SetString(PyExc_RuntimeError, exn.what()); 
    } 
    catch (...) 
    { 
    PyErr_SetString(PyExc_RuntimeError, "Unknown exception"); 
    } 
} 

por lo que puede #define __Pyx_CppExn2PyErr your_custom_exn_handler en un archivo .h incluido para reemplazar el comportamiento genérico, o el uso un controlador personalizado único como

cdef extern from "...": 
    void your_exn_throwing_fcn() except +your_custom_exn_handler 
7

De acuerdo en que la redacción en la página de documentos deja algo que desear. Mientras que "Cython no puede lanzar las excepciones de C++", aquí hay un raise_py_error que hace lo que queremos.

En primer lugar, definir la clase de excepción personalizada en Cython y hacer un mango para que el uso de la palabra clave "pública"

from cpython.ref cimport PyObject 

class JMapError(RuntimeError): 
    pass 

cdef public PyObject* jmaperror = <PyObject*>JMapError 

Luego escribir el manejador de excepciones (los documentos no son súper claro que esto debe ser escrito en C++ e importados):

#include "Python.h" 
#include "jmap/cy_utils.H" 
#include "jmap/errors.H" 
#include <exception> 
#include <string> 

using namespace std; 

extern PyObject *jmaperror; 

void raise_py_error() 
{ 
    try { 
    throw; 
    } catch (JMapError& e) { 
    string msg = ::to_string(e.code()) +" "+ e.what(); 
    PyErr_SetString(jmaperror, msg.c_str()); 
    } catch (const std::exception& e) { 
    PyErr_SetString(PyExc_RuntimeError, e.what()); 
    } 
} 

por último, llevar el manejador en Cython con un bloque externo, y lo utilizan:

cdef extern from "jmap/cy_utils.H": 
    cdef void raise_py_error() 

void _connect "connect"() except +raise_py_error 

Hecho. Ahora veo una nueva excepción, construida con el código de error como estaba previsto:

JMapError: 520 timed connect failed: Connection refused 
+0

Respuesta interesante. ¿Sabes si es posible hacer que cython compile ese archivo C++ directamente o necesito crear manualmente una lib y luego vincularla? No pude encontrar ninguna documentación sobre esto ... –

+0

Me enteré. Aquí hay un ejemplo mínimo de manejo de errores mientras se conserva el mensaje de la recepción personalizada: https://github.com/SintefRaufossManufacturing/python-hcn/blob/master/hcn/cy_handler.cpp –

+1

¿Podría explicar cómo exactamente (qué extensión de archivo, ¿Qué importaciones necesarias) utilizas para declarar la clase cython y convertirla en un 'PyObject'? (primer bloque de código) PyObject no está definido por defecto e importándolo de '" Python.h "' hace que cython se queje de que los objetos de Python no se pueden convertir a tipos primitivos ... – Silmathoron