Skip to content

Commit d8ab486

Browse files
committed
Update documentation for new local exception feature
1 parent d989940 commit d8ab486

File tree

1 file changed

+66
-7
lines changed

1 file changed

+66
-7
lines changed

docs/advanced/exceptions.rst

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ Registering custom translators
7575

7676
If the default exception conversion policy described above is insufficient,
7777
pybind11 also provides support for registering custom exception translators.
78-
To register a simple exception conversion that translates a C++ exception into
79-
a new Python exception using the C++ exception's ``what()`` method, a helper
80-
function is available:
78+
Similar to pybind11 classes, exception translators can be local to the module
79+
they are defined in or global to the entire python session. To register a simple
80+
exception conversion that translates a C++ exception into a new Python exception
81+
using the C++ exception's ``what()`` method, a helper function is available:
8182

8283
.. code-block:: cpp
8384
@@ -87,29 +88,39 @@ This call creates a Python exception class with the name ``PyExp`` in the given
8788
module and automatically converts any encountered exceptions of type ``CppExp``
8889
into Python exceptions of type ``PyExp``.
8990

91+
A matching function is available for registering a local exception translator:
92+
93+
.. code-block:: cpp
94+
95+
py::register_local_exception<CppExp>(module, "PyExp");
96+
97+
9098
It is possible to specify base class for the exception using the third
9199
parameter, a `handle`:
92100

93101
.. code-block:: cpp
94102
95103
py::register_exception<CppExp>(module, "PyExp", PyExc_RuntimeError);
104+
py::register_local_exception<CppExp>(module, "PyExp", PyExc_RuntimeError);
96105
97106
Then `PyExp` can be caught both as `PyExp` and `RuntimeError`.
98107

99108
The class objects of the built-in Python exceptions are listed in the Python
100109
documentation on `Standard Exceptions <https://docs.python.org/3/c-api/exceptions.html#standard-exceptions>`_.
101110
The default base class is `PyExc_Exception`.
102111

103-
When more advanced exception translation is needed, the function
104-
``py::register_exception_translator(translator)`` can be used to register
112+
When more advanced exception translation is needed, the functions
113+
``py::register_exception_translator(translator)`` and
114+
``py::register_local_exception_translator(translator)`` can be used to register
105115
functions that can translate arbitrary exception types (and which may include
106-
additional logic to do so). The function takes a stateless callable (e.g. a
116+
additional logic to do so). The functions takes a stateless callable (e.g. a
107117
function pointer or a lambda function without captured variables) with the call
108118
signature ``void(std::exception_ptr)``.
109119

110120
When a C++ exception is thrown, the registered exception translators are tried
111121
in reverse order of registration (i.e. the last registered translator gets the
112-
first shot at handling the exception).
122+
first shot at handling the exception). All local translators will be tried
123+
before a global translator is tried.
113124

114125
Inside the translator, ``std::rethrow_exception`` should be used within
115126
a try block to re-throw the exception. One or more catch clauses to catch
@@ -164,6 +175,54 @@ section.
164175
may be explicitly (re-)thrown to delegate it to the other,
165176
previously-declared existing exception translators.
166177

178+
Local vs Global Exception Translators
179+
==============================
180+
181+
Similar to how the ``py::module_local`` flag allows uesrs to limit a bound class
182+
to a single compiled module to prevent name collisions. When a global exception
183+
translator is registered, it will be applied across all modules in the reverse
184+
order of registration. This can create behavior where the order of module import
185+
influences how exceptions are translated.
186+
187+
If module1 has the following translator:
188+
189+
.. code-block:: cpp
190+
191+
py::register_exception_translator([](std::exception_ptr p) {
192+
try {
193+
if (p) std::rethrow_exception(p);
194+
} catch (const std::invalid_argument &e) {
195+
PyErr_SetString("module1 handled this")
196+
}
197+
}
198+
199+
and module2 has the following similar translator:
200+
201+
.. code-block:: cpp
202+
203+
py::register_exception_translator([](std::exception_ptr p) {
204+
try {
205+
if (p) std::rethrow_exception(p);
206+
} catch (const std::invalid_argument &e) {
207+
PyErr_SetString("module2 handled this")
208+
}
209+
}
210+
211+
then which translator handles the invalid_argument will be determined by the
212+
order that module1 and module2 are imported. Since exception translators are
213+
applied in the reverse order of registration, which ever module was imported
214+
last will "win" and that translator will be applied.
215+
216+
If there are multiple pybind11 modules that share exception types (either
217+
standard built-in or custom) loaded into a single python instance and
218+
consistent error handling behavior is needed, then local translators should be
219+
used.
220+
221+
Changing the previous example to use register_local_exception_translator would
222+
mean that when invalid_argument is thrown in the module2 code, the module2
223+
translator will always handle it, while in module1, the module1 translator will
224+
do the same.
225+
167226
.. _handling_python_exceptions_cpp:
168227

169228
Handling exceptions from Python in C++

0 commit comments

Comments
 (0)