mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 14:45:12 +00:00
Added py::register_exception for simple case (#296)
The custom exception handling added in PR #273 is robust, but is overly complex for declaring the most common simple C++ -> Python exception mapping that needs only to copy `what()`. This add a simpler `py::register_exception<CppExp>(module, "PyExp");` function that greatly simplifies the common basic case of translation of a simple CppException into a simple PythonException, while not removing the more advanced capabilities of defining custom exception handlers.
This commit is contained in:
parent
29b5064e9c
commit
b3794f1087
@ -1228,56 +1228,82 @@ If the default exception conversion policy described
|
|||||||
is insufficient, pybind11 also provides support for registering custom
|
is insufficient, pybind11 also provides support for registering custom
|
||||||
exception translators.
|
exception translators.
|
||||||
|
|
||||||
The function ``register_exception_translator(translator)`` takes a stateless
|
To register a simple exception conversion that translates a C++ exception into
|
||||||
callable (e.g. a function pointer or a lambda function without captured
|
a new Python exception using the C++ exception's ``what()`` method, a helper
|
||||||
variables) with the following call signature: ``void(std::exception_ptr)``.
|
function is available:
|
||||||
|
|
||||||
When a C++ exception is thrown, registered exception translators are tried
|
|
||||||
in reverse order of registration (i.e. the last registered translator gets
|
|
||||||
a first shot at handling the exception).
|
|
||||||
|
|
||||||
Inside the translator, ``std::rethrow_exception`` should be used within
|
|
||||||
a try block to re-throw the exception. A catch clause can then use
|
|
||||||
``PyErr_SetString`` to set a Python exception as demonstrated
|
|
||||||
in :file:`tests/test_exceptions.cpp`.
|
|
||||||
|
|
||||||
This example also demonstrates how to create custom exception types
|
|
||||||
with ``py::exception``.
|
|
||||||
|
|
||||||
The following example demonstrates this for a hypothetical exception class
|
|
||||||
``MyCustomException``:
|
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
py::register_exception<CppExp>(module, "PyExp");
|
||||||
|
|
||||||
|
This call creates a Python exception class with the name ``PyExp`` in the given
|
||||||
|
module and automatically converts any encountered exceptions of type ``CppExp``
|
||||||
|
into Python exceptions of type ``PyExp``.
|
||||||
|
|
||||||
|
When more advanced exception translation is needed, the function
|
||||||
|
``py::register_exception_translator(translator)`` can be used to register
|
||||||
|
functions that can translate arbitrary exception types (and which may include
|
||||||
|
additional logic to do so). The function takes a stateless callable (e.g. a
|
||||||
|
function pointer or a lambda function without captured variables) with the call
|
||||||
|
signature ``void(std::exception_ptr)``.
|
||||||
|
|
||||||
|
When a C++ exception is thrown, the registered exception translators are tried
|
||||||
|
in reverse order of registration (i.e. the last registered translator gets the
|
||||||
|
first shot at handling the exception).
|
||||||
|
|
||||||
|
Inside the translator, ``std::rethrow_exception`` should be used within
|
||||||
|
a try block to re-throw the exception. One or more catch clauses to catch
|
||||||
|
the appropriate exceptions should then be used with each clause using
|
||||||
|
``PyErr_SetString`` to set a Python exception or ``ex(string)`` to set
|
||||||
|
the python exception to a custom exception type (see below).
|
||||||
|
|
||||||
|
To declare a custom Python exception type, declare a ``py::exception`` variable
|
||||||
|
and use this in the associated exception translator (note: it is often useful
|
||||||
|
to make this a static declaration when using it inside a lambda expression
|
||||||
|
without requiring capturing).
|
||||||
|
|
||||||
|
|
||||||
|
The following example demonstrates this for a hypothetical exception classes
|
||||||
|
``MyCustomException`` and ``OtherException``: the first is translated to a
|
||||||
|
custom python exception ``MyCustomError``, while the second is translated to a
|
||||||
|
standard python RuntimeError:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
static py::exception<MyCustomException> exc(m, "MyCustomError");
|
||||||
py::register_exception_translator([](std::exception_ptr p) {
|
py::register_exception_translator([](std::exception_ptr p) {
|
||||||
try {
|
try {
|
||||||
if (p) std::rethrow_exception(p);
|
if (p) std::rethrow_exception(p);
|
||||||
} catch (const MyCustomException &e) {
|
} catch (const MyCustomException &e) {
|
||||||
|
exc(e.what());
|
||||||
|
} catch (const OtherException &e) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, e.what());
|
PyErr_SetString(PyExc_RuntimeError, e.what());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Multiple exceptions can be handled by a single translator. If the exception is
|
Multiple exceptions can be handled by a single translator, as shown in the
|
||||||
not caught by the current translator, the previously registered one gets a
|
example above. If the exception is not caught by the current translator, the
|
||||||
chance.
|
previously registered one gets a chance.
|
||||||
|
|
||||||
If none of the registered exception translators is able to handle the
|
If none of the registered exception translators is able to handle the
|
||||||
exception, it is handled by the default converter as described in the previous
|
exception, it is handled by the default converter as described in the previous
|
||||||
section.
|
section.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
The file :file:`tests/test_exceptions.cpp` contains examples
|
||||||
|
of various custom exception translators and custom exception types.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
You must either call ``PyErr_SetString`` for every exception caught in a
|
You must call either ``PyErr_SetString`` or a custom exception's call
|
||||||
custom exception translator. Failure to do so will cause Python to crash
|
operator (``exc(string)``) for every exception caught in a custom exception
|
||||||
with ``SystemError: error return without exception set``.
|
translator. Failure to do so will cause Python to crash with ``SystemError:
|
||||||
|
error return without exception set``.
|
||||||
|
|
||||||
Exceptions that you do not plan to handle should simply not be caught.
|
Exceptions that you do not plan to handle should simply not be caught, or
|
||||||
|
may be explicity (re-)thrown to delegate it to the other,
|
||||||
You may also choose to explicity (re-)throw the exception to delegate it to
|
previously-declared existing exception translators.
|
||||||
the other existing exception translators.
|
|
||||||
|
|
||||||
The ``py::exception`` wrapper for creating custom exceptions cannot (yet)
|
|
||||||
be used as a base type.
|
|
||||||
|
|
||||||
.. _eigen:
|
.. _eigen:
|
||||||
|
|
||||||
|
@ -1281,7 +1281,7 @@ void register_exception_translator(ExceptionTranslator&& translator) {
|
|||||||
template <typename type>
|
template <typename type>
|
||||||
class exception : public object {
|
class exception : public object {
|
||||||
public:
|
public:
|
||||||
exception(module &m, const std::string name, PyObject* base=PyExc_Exception) {
|
exception(module &m, const std::string &name, PyObject* base=PyExc_Exception) {
|
||||||
std::string full_name = std::string(PyModule_GetName(m.ptr()))
|
std::string full_name = std::string(PyModule_GetName(m.ptr()))
|
||||||
+ std::string(".") + name;
|
+ std::string(".") + name;
|
||||||
char* exception_name = const_cast<char*>(full_name.c_str());
|
char* exception_name = const_cast<char*>(full_name.c_str());
|
||||||
@ -1289,8 +1289,32 @@ public:
|
|||||||
inc_ref(); // PyModule_AddObject() steals a reference
|
inc_ref(); // PyModule_AddObject() steals a reference
|
||||||
PyModule_AddObject(m.ptr(), name.c_str(), m_ptr);
|
PyModule_AddObject(m.ptr(), name.c_str(), m_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets the current python exception to this exception object with the given message
|
||||||
|
void operator()(const char *message) {
|
||||||
|
PyErr_SetString(m_ptr, message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Registers a Python exception in `m` of the given `name` and installs an exception translator to
|
||||||
|
* translate the C++ exception to the created Python exception using the exceptions what() method.
|
||||||
|
* This is intended for simple exception translations; for more complex translation, register the
|
||||||
|
* exception object and translator directly.
|
||||||
|
*/
|
||||||
|
template <typename CppException> exception<CppException>& register_exception(module &m, const std::string &name, PyObject* base = PyExc_Exception) {
|
||||||
|
static exception<CppException> ex(m, name, base);
|
||||||
|
register_exception_translator([](std::exception_ptr p) {
|
||||||
|
if (!p) return;
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(p);
|
||||||
|
}
|
||||||
|
catch (const CppException &e) {
|
||||||
|
ex(e.what());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
|
||||||
NAMESPACE_BEGIN(detail)
|
NAMESPACE_BEGIN(detail)
|
||||||
PYBIND11_NOINLINE inline void print(tuple args, dict kwargs) {
|
PYBIND11_NOINLINE inline void print(tuple args, dict kwargs) {
|
||||||
auto strings = tuple(args.size());
|
auto strings = tuple(args.size());
|
||||||
|
@ -46,6 +46,18 @@ private:
|
|||||||
std::string message = "";
|
std::string message = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Like the above, but declared via the helper function
|
||||||
|
class MyException5 : public std::logic_error {
|
||||||
|
public:
|
||||||
|
explicit MyException5(const std::string &what) : std::logic_error(what) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inherits from MyException5
|
||||||
|
class MyException5_1 : public MyException5 {
|
||||||
|
using MyException5::MyException5;
|
||||||
|
};
|
||||||
|
|
||||||
void throws1() {
|
void throws1() {
|
||||||
throw MyException("this error should go to a custom type");
|
throw MyException("this error should go to a custom type");
|
||||||
}
|
}
|
||||||
@ -62,6 +74,14 @@ void throws4() {
|
|||||||
throw MyException4("this error is rethrown");
|
throw MyException4("this error is rethrown");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void throws5() {
|
||||||
|
throw MyException5("this is a helper-defined translated exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
void throws5_1() {
|
||||||
|
throw MyException5_1("MyException5 subclass");
|
||||||
|
}
|
||||||
|
|
||||||
void throws_logic_error() {
|
void throws_logic_error() {
|
||||||
throw std::logic_error("this error should fall through to the standard handler");
|
throw std::logic_error("this error should fall through to the standard handler");
|
||||||
}
|
}
|
||||||
@ -80,7 +100,8 @@ test_initializer custom_exceptions([](py::module &m) {
|
|||||||
try {
|
try {
|
||||||
if (p) std::rethrow_exception(p);
|
if (p) std::rethrow_exception(p);
|
||||||
} catch (const MyException &e) {
|
} catch (const MyException &e) {
|
||||||
PyErr_SetString(ex.ptr(), e.what());
|
// Set MyException as the active python error
|
||||||
|
ex(e.what());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -91,6 +112,7 @@ test_initializer custom_exceptions([](py::module &m) {
|
|||||||
try {
|
try {
|
||||||
if (p) std::rethrow_exception(p);
|
if (p) std::rethrow_exception(p);
|
||||||
} catch (const MyException2 &e) {
|
} catch (const MyException2 &e) {
|
||||||
|
// Translate this exception to a standard RuntimeError
|
||||||
PyErr_SetString(PyExc_RuntimeError, e.what());
|
PyErr_SetString(PyExc_RuntimeError, e.what());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -106,10 +128,17 @@ test_initializer custom_exceptions([](py::module &m) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// A simple exception translation:
|
||||||
|
auto ex5 = py::register_exception<MyException5>(m, "MyException5");
|
||||||
|
// A slightly more complicated one that declares MyException5_1 as a subclass of MyException5
|
||||||
|
py::register_exception<MyException5_1>(m, "MyException5_1", ex5.ptr());
|
||||||
|
|
||||||
m.def("throws1", &throws1);
|
m.def("throws1", &throws1);
|
||||||
m.def("throws2", &throws2);
|
m.def("throws2", &throws2);
|
||||||
m.def("throws3", &throws3);
|
m.def("throws3", &throws3);
|
||||||
m.def("throws4", &throws4);
|
m.def("throws4", &throws4);
|
||||||
|
m.def("throws5", &throws5);
|
||||||
|
m.def("throws5_1", &throws5_1);
|
||||||
m.def("throws_logic_error", &throws_logic_error);
|
m.def("throws_logic_error", &throws_logic_error);
|
||||||
|
|
||||||
m.def("throw_already_set", [](bool err) {
|
m.def("throw_already_set", [](bool err) {
|
||||||
|
@ -22,7 +22,8 @@ def test_python_call_in_catch():
|
|||||||
|
|
||||||
|
|
||||||
def test_custom(msg):
|
def test_custom(msg):
|
||||||
from pybind11_tests import (MyException, throws1, throws2, throws3, throws4,
|
from pybind11_tests import (MyException, MyException5, MyException5_1,
|
||||||
|
throws1, throws2, throws3, throws4, throws5, throws5_1,
|
||||||
throws_logic_error)
|
throws_logic_error)
|
||||||
|
|
||||||
# Can we catch a MyException?"
|
# Can we catch a MyException?"
|
||||||
@ -49,3 +50,25 @@ def test_custom(msg):
|
|||||||
with pytest.raises(RuntimeError) as excinfo:
|
with pytest.raises(RuntimeError) as excinfo:
|
||||||
throws_logic_error()
|
throws_logic_error()
|
||||||
assert msg(excinfo.value) == "this error should fall through to the standard handler"
|
assert msg(excinfo.value) == "this error should fall through to the standard handler"
|
||||||
|
|
||||||
|
# Can we handle a helper-declared exception?
|
||||||
|
with pytest.raises(MyException5) as excinfo:
|
||||||
|
throws5()
|
||||||
|
assert msg(excinfo.value) == "this is a helper-defined translated exception"
|
||||||
|
|
||||||
|
# Exception subclassing:
|
||||||
|
with pytest.raises(MyException5) as excinfo:
|
||||||
|
throws5_1()
|
||||||
|
assert msg(excinfo.value) == "MyException5 subclass"
|
||||||
|
assert isinstance(excinfo.value, MyException5_1)
|
||||||
|
|
||||||
|
with pytest.raises(MyException5_1) as excinfo:
|
||||||
|
throws5_1()
|
||||||
|
assert msg(excinfo.value) == "MyException5 subclass"
|
||||||
|
|
||||||
|
with pytest.raises(MyException5) as excinfo:
|
||||||
|
try:
|
||||||
|
throws5()
|
||||||
|
except MyException5_1 as e:
|
||||||
|
raise RuntimeError("Exception error: caught child from parent")
|
||||||
|
assert msg(excinfo.value) == "this is a helper-defined translated exception"
|
||||||
|
Loading…
Reference in New Issue
Block a user