2012-05-21 12 views
6

Ho alcuni problemi nella gestione delle eccezioni C++ personalizzate quando si chiama da Cython. La mia situazione è la seguente: Ho una libreria che utilizza CustomLibraryException per tutte le eccezioni. Quello che voglio è fondamentalmente ottenere il messaggio di errore e sollevare un errore Python con esso.Gestione di eccezioni C++ personalizzate in Cython

Il user guide ha alcuni suggerimenti ma è un po 'non specifico. La prima possibilità è quella di fare:

cdef int bar() ad eccezione ValueError +

Questo converte il CustomLibraryException ad un ValueError, ma perde il messaggio di errore.

L'altra possibilità è quella di convertire in modo esplicito l'errore utilizzando

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

Non mi understant questa soluzione. Ho capito che raise_py_error deve essere una funzione C++ che in qualche modo gestisce l'errore. Non sono sicuro di come gestirlo però. La funzione non ottiene un argomento e viene chiamata all'interno del blocco catch in C++.

Se qualcuno ha un esempio funzionante di gestione di un'eccezione C++ in Cython, sarebbe di grande aiuto.

risposta

2

Se CustomLibraryException deriva da std::runtime_error (come dovrebbe essere un'eccezione C++ ben educata), il comportamento che si sta verificando è un errore in Cython.

Se così non fosse, allora la cosa più semplice da fare è quello di avvolgere la funzione C++ che stai chiamando in una funzione C++ di supporto che traduce l'eccezione:

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

Ciò causerà un RuntimeError di essere sollevato dal lato di Python. In alternativa, throw un std::invalid_argument a raise a ValueError, ecc. (Vedere la tabella nella pagina a cui è collegato).

+0

L'eccezione non deriva da '' std :: runtime'' come previsto. Grazie per l'aiuto :) Questo non lo rende molto meglio, però. Le funzioni che sollevano l'errore sono funzioni membro e non voglio cambiare il loro codice. Si tratta dei miei [gco wrapper] (http://peekaboo-vision.blogspot.de/2012/05/graphcuts-for-python-pygco.html) e la licenza non mi consente di ridistribuire: -/ –

+1

@ AndreasMueller: le funzioni membro possono essere facilmente racchiuse in funzioni indipendenti; basta passare l'oggetto su cui dovrebbero operare come primo argomento: 'void wrapper (Obj & o, int ham) {return o.wrapped (ham); } ' –

+1

Sì, lo so, ero solo pigro per farlo ancora;) Btw, lo sapevi che sei il 100 ° poster per SO? –

3

Il valore di default C++ gestore di eccezioni in Cython dovrebbe illustrare esattamente come realizzare ciò che si sta cercando di fare:

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

Quindi è possibile #define __Pyx_CppExn2PyErr your_custom_exn_handler in un file .h incluso per ignorare il comportamento generico, o l'uso un gestore personalizzato one-off come

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

concordato il testo nella pagina doc lascia un po 'a desiderare. Mentre "Cython non può lanciare eccezioni C++", ecco un raise_py_error che fa ciò che vogliamo.

In primo luogo, definire la classe di eccezione personalizzata in Cython e fare un manico ad esso utilizzando la parola chiave "pubblica"

from cpython.ref cimport PyObject 

class JMapError(RuntimeError): 
    pass 

cdef public PyObject* jmaperror = <PyObject*>JMapError 

Quindi scrivere il gestore di eccezioni (i documenti non sono super chiaro che questo deve essere scritto in C++ e importati):

#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()); 
    } 
} 

Infine, portare il gestore in Cython con un blocco esterno, e usarlo:

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

void _connect "connect"() except +raise_py_error 

Fatto. Ora vedo nuova eccezione, costruito con il codice di errore come previsto:

JMapError: 520 timed connect failed: Connection refused 
+0

Risposta interrotta. Sai se è possibile fare in modo che cython compili direttamente il file C++ o devo fare manualmente una lib link quindi contro di esso? Non sono riuscito a trovare alcuna documentazione su questo ... –

+0

L'ho scoperto. Ecco un esempio minimale di gestione degli errori preservando il messaggio dall'eccezione personalizzata: https://github.com/SintefRaufossManufacturing/python-hcn/blob/master/hcn/cy_handler.cpp –

+1

Potrebbe spiegare come esattamente (quale estensione di file, quali importazioni necessarie) usi per dichiarare la classe cython e lanciarla su un 'PyObject'? (first codeblock) PyObject non è definito di default e lo importa da "" Python.h "' make cython si lamenta che gli oggetti Python non possono essere castati in tipi primitivi ... – Silmathoron