@@ -75,9 +75,10 @@ Registering custom translators
75
75
76
76
If the default exception conversion policy described above is insufficient,
77
77
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:
81
82
82
83
.. code-block :: cpp
83
84
@@ -87,29 +88,39 @@ This call creates a Python exception class with the name ``PyExp`` in the given
87
88
module and automatically converts any encountered exceptions of type ``CppExp ``
88
89
into Python exceptions of type ``PyExp ``.
89
90
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
+
90
98
It is possible to specify base class for the exception using the third
91
99
parameter, a `handle `:
92
100
93
101
.. code-block :: cpp
94
102
95
103
py::register_exception<CppExp>(module, "PyExp", PyExc_RuntimeError);
104
+ py::register_local_exception<CppExp>(module, "PyExp", PyExc_RuntimeError);
96
105
97
106
Then `PyExp ` can be caught both as `PyExp ` and `RuntimeError `.
98
107
99
108
The class objects of the built-in Python exceptions are listed in the Python
100
109
documentation on `Standard Exceptions <https://docs.python.org/3/c-api/exceptions.html#standard-exceptions >`_.
101
110
The default base class is `PyExc_Exception `.
102
111
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
105
115
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
107
117
function pointer or a lambda function without captured variables) with the call
108
118
signature ``void(std::exception_ptr) ``.
109
119
110
120
When a C++ exception is thrown, the registered exception translators are tried
111
121
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.
113
124
114
125
Inside the translator, ``std::rethrow_exception `` should be used within
115
126
a try block to re-throw the exception. One or more catch clauses to catch
@@ -164,6 +175,54 @@ section.
164
175
may be explicitly (re-)thrown to delegate it to the other,
165
176
previously-declared existing exception translators.
166
177
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
+
167
226
.. _handling_python_exceptions_cpp :
168
227
169
228
Handling exceptions from Python in C++
0 commit comments