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
|
||||
exception translators.
|
||||
|
||||
The function ``register_exception_translator(translator)`` takes a stateless
|
||||
callable (e.g. a function pointer or a lambda function without captured
|
||||
variables) with the following call signature: ``void(std::exception_ptr)``.
|
||||
|
||||
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``:
|
||||
To register a simple exception conversion that translates a C++ exception into
|
||||
a new Python exception using the C++ exception's ``what()`` method, a helper
|
||||
function is available:
|
||||
|
||||
.. 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) {
|
||||
try {
|
||||
if (p) std::rethrow_exception(p);
|
||||
} catch (const MyCustomException &e) {
|
||||
exc(e.what());
|
||||
} catch (const OtherException &e) {
|
||||
PyErr_SetString(PyExc_RuntimeError, e.what());
|
||||
}
|
||||
});
|
||||
|
||||
Multiple exceptions can be handled by a single translator. If the exception is
|
||||
not caught by the current translator, the previously registered one gets a
|
||||
chance.
|
||||
Multiple exceptions can be handled by a single translator, as shown in the
|
||||
example above. If the exception is not caught by the current translator, the
|
||||
previously registered one gets a chance.
|
||||
|
||||
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
|
||||
section.
|
||||
|
||||
.. seealso::
|
||||
|
||||
The file :file:`tests/test_exceptions.cpp` contains examples
|
||||
of various custom exception translators and custom exception types.
|
||||
|
||||
.. note::
|
||||
|
||||
You must either call ``PyErr_SetString`` for every exception caught in a
|
||||
custom exception translator. Failure to do so will cause Python to crash
|
||||
with ``SystemError: error return without exception set``.
|
||||
You must call either ``PyErr_SetString`` or a custom exception's call
|
||||
operator (``exc(string)``) for every exception caught in a custom exception
|
||||
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.
|
||||
|
||||
You may also choose to explicity (re-)throw the exception to delegate it to
|
||||
the other existing exception translators.
|
||||
|
||||
The ``py::exception`` wrapper for creating custom exceptions cannot (yet)
|
||||
be used as a base type.
|
||||
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,
|
||||
previously-declared existing exception translators.
|
||||
|
||||
.. _eigen:
|
||||
|
||||
|
@ -1281,7 +1281,7 @@ void register_exception_translator(ExceptionTranslator&& translator) {
|
||||
template <typename type>
|
||||
class exception : public object {
|
||||
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(".") + name;
|
||||
char* exception_name = const_cast<char*>(full_name.c_str());
|
||||
@ -1289,8 +1289,32 @@ public:
|
||||
inc_ref(); // PyModule_AddObject() steals a reference
|
||||
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)
|
||||
PYBIND11_NOINLINE inline void print(tuple args, dict kwargs) {
|
||||
auto strings = tuple(args.size());
|
||||
|
@ -46,6 +46,18 @@ private:
|
||||
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() {
|
||||
throw MyException("this error should go to a custom type");
|
||||
}
|
||||
@ -62,6 +74,14 @@ void throws4() {
|
||||
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() {
|
||||
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 {
|
||||
if (p) std::rethrow_exception(p);
|
||||
} 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 {
|
||||
if (p) std::rethrow_exception(p);
|
||||
} catch (const MyException2 &e) {
|
||||
// Translate this exception to a standard RuntimeError
|
||||
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("throws2", &throws2);
|
||||
m.def("throws3", &throws3);
|
||||
m.def("throws4", &throws4);
|
||||
m.def("throws5", &throws5);
|
||||
m.def("throws5_1", &throws5_1);
|
||||
m.def("throws_logic_error", &throws_logic_error);
|
||||
|
||||
m.def("throw_already_set", [](bool err) {
|
||||
|
@ -22,7 +22,8 @@ def test_python_call_in_catch():
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# Can we catch a MyException?"
|
||||
@ -49,3 +50,25 @@ def test_custom(msg):
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
throws_logic_error()
|
||||
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