1
1
Exceptions
2
2
##########
3
3
4
- Built-in exception translation
5
- ==============================
4
+ Built-in C++ to Python exception translation
5
+ ============================================
6
+
7
+ When Python calls C++ code through pybind11, pybind11 provides a C++ exception handler
8
+ that will trap C++ exceptions, translate them to the corresponding Python exception,
9
+ and raise them so that Python code can handle them.
6
10
7
- When C++ code invoked from Python throws an ``std::exception ``, it is
8
- automatically converted into a Python ``Exception ``. pybind11 defines multiple
9
- special exception classes that will map to different types of Python
10
- exceptions:
11
+ pybind11 defines translations for ``std::exception `` and its standard
12
+ subclasses, and several special exception classes that translate to specific
13
+ Python exceptions. Note that these are not actually Python exceptions, so they
14
+ cannot be examined using the Python C API. Instead, they are pure C++ objects
15
+ that pybind11 will translate the corresponding Python exception when they arrive
16
+ at its exception handler.
11
17
12
18
.. tabularcolumns :: |p{0.5\textwidth}|p{0.45\textwidth}|
13
19
14
20
+--------------------------------------+--------------------------------------+
15
- | C++ exception type | Python exception type |
21
+ | Exception thrown by C++ | Translated to Python exception type |
16
22
+======================================+======================================+
17
23
| :class: `std::exception ` | ``RuntimeError `` |
18
24
+--------------------------------------+--------------------------------------+
@@ -46,22 +52,11 @@ exceptions:
46
52
| | ``__setitem__ `` in dict-like |
47
53
| | objects, etc.) |
48
54
+--------------------------------------+--------------------------------------+
49
- | :class: `pybind11::error_already_set ` | Indicates that the Python exception |
50
- | | flag has already been set via Python |
51
- | | API calls from C++ code; this C++ |
52
- | | exception is used to propagate such |
53
- | | a Python exception back to Python. |
54
- +--------------------------------------+--------------------------------------+
55
55
56
- When a Python function invoked from C++ throws an exception, pybind11 will convert
57
- it into a C++ exception of type :class: `error_already_set ` whose string payload
58
- contains a textual summary. If you call the Python C-API directly, and it
59
- returns an error, you should ``throw py::error_already_set(); ``, which allows
60
- pybind11 to deal with the exception and pass it back to the Python interpreter.
61
- (Another option is to call ``PyErr_Clear `` in the
62
- `Python C-API <https://docs.python.org/3/c-api/exceptions.html#c.PyErr_Clear >`_
63
- to clear the error. The Python error must be thrown or cleared, or Python/pybind11
64
- will be left in an invalid state.)
56
+ Exception translation is not bidirectional. That is, *catching * the C++
57
+ exceptions defined above above will not trap exceptions that originate from
58
+ Python. For that, catch :class: `pybind11::error_already_set `. See :ref: `below
59
+ <handling_python_exceptions_cpp>` for further details.
65
60
66
61
There is also a special exception :class: `cast_error ` that is thrown by
67
62
:func: `handle::call ` when the input arguments cannot be converted to Python
@@ -106,7 +101,6 @@ and use this in the associated exception translator (note: it is often useful
106
101
to make this a static declaration when using it inside a lambda expression
107
102
without requiring capturing).
108
103
109
-
110
104
The following example demonstrates this for a hypothetical exception classes
111
105
``MyCustomException `` and ``OtherException ``: the first is translated to a
112
106
custom python exception ``MyCustomError ``, while the second is translated to a
@@ -140,7 +134,7 @@ section.
140
134
141
135
.. note ::
142
136
143
- You must call either ``PyErr_SetString `` or a custom exception's call
137
+ Call either ``PyErr_SetString `` or a custom exception's call
144
138
operator (``exc(string) ``) for every exception caught in a custom exception
145
139
translator. Failure to do so will cause Python to crash with ``SystemError:
146
140
error return without exception set ``.
@@ -149,27 +143,128 @@ section.
149
143
may be explicitly (re-)thrown to delegate it to the other,
150
144
previously-declared existing exception translators.
151
145
146
+ .. _handling_python_exceptions_cpp :
147
+
148
+ Handling exceptions from Python in C++
149
+ ======================================
150
+
151
+ When C++ calls Python functions, such as in a callback function or when
152
+ manipulating Python objects, and Python raises an ``Exception ``, pybind11
153
+ converts the Python exception into a C++ exception of type
154
+ :class: `pybind11::error_already_set ` whose payload contains a C++ string textual
155
+ summary and the actual Python exception. ``error_already_set `` is used to
156
+ propagate Python exception back to Python (or possibly, handle them in C++).
157
+
158
+ .. tabularcolumns :: |p{0.5\textwidth}|p{0.45\textwidth}|
159
+
160
+ +--------------------------------------+--------------------------------------+
161
+ | Exception raised in Python | Thrown as C++ exception type |
162
+ +======================================+======================================+
163
+ | Any Python ``Exception `` | :class: `pybind11::error_already_set ` |
164
+ +--------------------------------------+--------------------------------------+
165
+
166
+ For example:
167
+
168
+ .. code-block :: cpp
169
+
170
+ try {
171
+ // open("missing.txt", "r")
172
+ auto file = py::module::import("io").attr("open")("missing.txt", "r");
173
+ auto text = file.attr("read")();
174
+ file.attr("close")();
175
+ } catch (py::error_already_set &e) {
176
+ if (e.matches(PyExc_FileNotFoundError)) {
177
+ py::print("missing.txt not found");
178
+ } else if (e.match(PyExc_PermissionError)) {
179
+ py::print("missing.txt found but not accessible");
180
+ } else {
181
+ throw;
182
+ }
183
+ }
184
+
185
+ Note that C++ to Python exception translation does not apply here, since that is
186
+ a method for translating C++ exceptions to Python, not vice versa. The error raised
187
+ from Python is always ``error_already_set ``.
188
+
189
+ This example illustrates this behavior:
190
+
191
+ .. code-block :: cpp
192
+
193
+ try {
194
+ py::eval("raise ValueError('The Ring')");
195
+ } catch (py::value_error &boromir) {
196
+ // Boromir never gets the ring
197
+ assert(false);
198
+ } catch (py::error_already_set &frodo) {
199
+ // Frodo gets the ring
200
+ py::print("I will take the ring");
201
+ }
202
+
203
+ try {
204
+ // py::value_error is a request for pybind11 to raise a Python exception
205
+ throw py::value_error("The ball");
206
+ } catch (py::error_already_set &cat) {
207
+ // cat won't catch the ball since
208
+ // py::value_error is not a Python exception
209
+ assert(false);
210
+ } catch (py::value_error &dog) {
211
+ // dog will catch the ball
212
+ py::print("Run Spot run");
213
+ throw; // Throw it again (pybind11 will raise ValueError)
214
+ }
215
+
216
+ Handling errors from the Python C API
217
+ =====================================
218
+
219
+ Where possible, use :ref: `pybind11 wrappers <wrappers >` instead of calling
220
+ the Python C API directly. When calling the Python C API directly, in
221
+ addition to manually managing reference counts, one must follow the pybind11
222
+ error protocol, which is outlined here.
223
+
224
+ After calling the Python C API, if Python returns an error,
225
+ ``throw py::error_already_set(); ``, which allows pybind11 to deal with the
226
+ exception and pass it back to the Python interpreter. This includes calls to
227
+ the error setting functions such as ``PyErr_SetString ``.
228
+
229
+ .. code-block :: cpp
230
+
231
+ PyErr_SetString(PyExc_TypeError, "C API type error demo");
232
+ throw py::error_already_set();
233
+
234
+ // But it would be easier to simply...
235
+ throw py::type_error("pybind11 wrapper type error");
236
+
237
+ Alternately, to ignore the error, call `PyErr_Clear
238
+ <https://docs.python.org/3/c-api/exceptions.html#c.PyErr_Clear> `_.
239
+
240
+ Any Python error must be thrown or cleared, or Python/pybind11 will be left in
241
+ an invalid state.
242
+
152
243
.. _unraisable_exceptions :
153
244
154
245
Handling unraisable exceptions
155
246
==============================
156
247
157
248
If a Python function invoked from a C++ destructor or any function marked
158
249
``noexcept(true) `` (collectively, "noexcept functions") throws an exception, there
159
- is no way to propagate the exception, as such functions may not throw at
160
- run-time.
250
+ is no way to propagate the exception, as such functions may not throw.
251
+ Should they throw or fail to catch any exceptions in their call graph,
252
+ the C++ runtime calls ``std::terminate() `` to abort immediately.
161
253
162
- Neither Python nor C++ allow exceptions raised in a noexcept function to propagate. In
163
- Python, an exception raised in a class's ``__del__ `` method is logged as an
164
- unraisable error. In Python 3.8+, a system hook is triggered and an auditing
165
- event is logged. In C++, ``std::terminate() `` is called to abort immediately.
254
+ Similarly, Python exceptions raised in a class's ``__del__ `` method do not
255
+ propagate, but are logged by Python as an unraisable error. In Python 3.8+, a
256
+ `system hook is triggered
257
+ <https://docs.python.org/3/library/sys.html#sys.unraisablehook> `_
258
+ and an auditing event is logged.
166
259
167
260
Any noexcept function should have a try-catch block that traps
168
- class:`error_already_set ` (or any other exception that can occur). Note that pybind11
169
- wrappers around Python exceptions such as :class: `pybind11::value_error ` are *not *
170
- Python exceptions; they are C++ exceptions that pybind11 catches and converts to
171
- Python exceptions. Noexcept functions cannot propagate these exceptions either.
172
- You can convert them to Python exceptions and then discard as unraisable.
261
+ class:`error_already_set ` (or any other exception that can occur). Note that
262
+ pybind11 wrappers around Python exceptions such as
263
+ :class: `pybind11::value_error ` are *not * Python exceptions; they are C++
264
+ exceptions that pybind11 catches and converts to Python exceptions. Noexcept
265
+ functions cannot propagate these exceptions either. A useful approach is to
266
+ convert them to Python exceptions and then ``discard_as_unraisable `` as shown
267
+ below.
173
268
174
269
.. code-block :: cpp
175
270
@@ -181,10 +276,10 @@ You can convert them to Python exceptions and then discard as unraisable.
181
276
// variable __func__. Python already knows the type and value and of the
182
277
// exception object.
183
278
eas.discard_as_unraisable(__func__);
184
- } catch (const std::exception &e) {
185
- // Log and discard C++ exceptions.
186
- // (We cannot use discard_as_unraisable, since we have a generic C++
187
- // exception, not an exception that originated from Python.)
188
- third_party::log(e );
279
+ } catch (py::value_error &e) {
280
+ // Convert pybind11 builtin exception to Python exception and disacard
281
+ e.set_error();
282
+ py::error_already_set eas;
283
+ eas.discard_as_unraisable(__func__ );
189
284
}
190
285
}
0 commit comments