aboutsummaryrefslogtreecommitdiffstats
path: root/3rdparty/pybind11/docs/advanced/exceptions.rst
blob: 5eae556247debd41e2db54d522f3a15de0f5d68f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
Exceptions
##########

Built-in C++ to Python exception translation
============================================

When Python calls C++ code through pybind11, pybind11 provides a C++ exception handler
that will trap C++ exceptions, translate them to the corresponding Python exception,
and raise them so that Python code can handle them.

pybind11 defines translations for ``std::exception`` and its standard
subclasses, and several special exception classes that translate to specific
Python exceptions. Note that these are not actually Python exceptions, so they
cannot be examined using the Python C API. Instead, they are pure C++ objects
that pybind11 will translate the corresponding Python exception when they arrive
at its exception handler.

.. tabularcolumns:: |p{0.5\textwidth}|p{0.45\textwidth}|

+--------------------------------------+--------------------------------------+
|  Exception thrown by C++             |  Translated to Python exception type |
+======================================+======================================+
| :class:`std::exception`              | ``RuntimeError``                     |
+--------------------------------------+--------------------------------------+
| :class:`std::bad_alloc`              | ``MemoryError``                      |
+--------------------------------------+--------------------------------------+
| :class:`std::domain_error`           | ``ValueError``                       |
+--------------------------------------+--------------------------------------+
| :class:`std::invalid_argument`       | ``ValueError``                       |
+--------------------------------------+--------------------------------------+
| :class:`std::length_error`           | ``ValueError``                       |
+--------------------------------------+--------------------------------------+
| :class:`std::out_of_range`           | ``IndexError``                       |
+--------------------------------------+--------------------------------------+
| :class:`std::range_error`            | ``ValueError``                       |
+--------------------------------------+--------------------------------------+
| :class:`std::overflow_error`         | ``OverflowError``                    |
+--------------------------------------+--------------------------------------+
| :class:`pybind11::stop_iteration`    | ``StopIteration`` (used to implement |
|                                      | custom iterators)                    |
+--------------------------------------+--------------------------------------+
| :class:`pybind11::index_error`       | ``IndexError`` (used to indicate out |
|                                      | of bounds access in ``__getitem__``, |
|                                      | ``__setitem__``, etc.)               |
+--------------------------------------+--------------------------------------+
| :class:`pybind11::value_error`       | ``ValueError`` (used to indicate     |
|                                      | wrong value passed in                |
|                                      | ``container.remove(...)``)           |
+--------------------------------------+--------------------------------------+
| :class:`pybind11::key_error`         | ``KeyError`` (used to indicate out   |
|                                      | of bounds access in ``__getitem__``, |
|                                      | ``__setitem__`` in dict-like         |
|                                      | objects, etc.)                       |
+--------------------------------------+--------------------------------------+

Exception translation is not bidirectional. That is, *catching* the C++
exceptions defined above above will not trap exceptions that originate from
Python. For that, catch :class:`pybind11::error_already_set`. See :ref:`below
<handling_python_exceptions_cpp>` for further details.

There is also a special exception :class:`cast_error` that is thrown by
:func:`handle::call` when the input arguments cannot be converted to Python
objects.

Registering custom translators
==============================

If the default exception conversion policy described above is insufficient,
pybind11 also provides support for registering custom exception translators.
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``.

It is possible to specify base class for the exception using the third
parameter, a `handle`:

.. code-block:: cpp

    py::register_exception<CppExp>(module, "PyExp", PyExc_RuntimeError);

Then `PyExp` can be caught both as `PyExp` and `RuntimeError`.

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

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, 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::

    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, or
    may be explicitly (re-)thrown to delegate it to the other,
    previously-declared existing exception translators.

.. _handling_python_exceptions_cpp:

Handling exceptions from Python in C++
======================================

When C++ calls Python functions, such as in a callback function or when
manipulating Python objects, and Python raises an ``Exception``, pybind11
converts the Python exception into a C++ exception of type
:class:`pybind11::error_already_set` whose payload contains a C++ string textual
summary and the actual Python exception. ``error_already_set`` is used to
propagate Python exception back to Python (or possibly, handle them in C++).

.. tabularcolumns:: |p{0.5\textwidth}|p{0.45\textwidth}|

+--------------------------------------+--------------------------------------+
|  Exception raised in Python          |  Thrown as C++ exception type        |
+======================================+======================================+
| Any Python ``Exception``             | :class:`pybind11::error_already_set` |
+--------------------------------------+--------------------------------------+

For example:

.. code-block:: cpp

    try {
        // open("missing.txt", "r")
        auto file = py::module_::import("io").attr("open")("missing.txt", "r");
        auto text = file.attr("read")();
        file.attr("close")();
    } catch (py::error_already_set &e) {
        if (e.matches(PyExc_FileNotFoundError)) {
            py::print("missing.txt not found");
        } else if (e.match(PyExc_PermissionError)) {
            py::print("missing.txt found but not accessible");
        } else {
            throw;
        }
    }

Note that C++ to Python exception translation does not apply here, since that is
a method for translating C++ exceptions to Python, not vice versa. The error raised
from Python is always ``error_already_set``.

This example illustrates this behavior:

.. code-block:: cpp

    try {
        py::eval("raise ValueError('The Ring')");
    } catch (py::value_error &boromir) {
        // Boromir never gets the ring
        assert(false);
    } catch (py::error_already_set &frodo) {
        // Frodo gets the ring
        py::print("I will take the ring");
    }

    try {
        // py::value_error is a request for pybind11 to raise a Python exception
        throw py::value_error("The ball");
    } catch (py::error_already_set &cat) {
        // cat won't catch the ball since
        // py::value_error is not a Python exception
        assert(false);
    } catch (py::value_error &dog) {
        // dog will catch the ball
        py::print("Run Spot run");
        throw;  // Throw it again (pybind11 will raise ValueError)
    }

Handling errors from the Python C API
=====================================

Where possible, use :ref:`pybind11 wrappers <wrappers>` instead of calling
the Python C API directly. When calling the Python C API directly, in
addition to manually managing reference counts, one must follow the pybind11
error protocol, which is outlined here.

After calling the Python C API, if Python returns an error,
``throw py::error_already_set();``, which allows pybind11 to deal with the
exception and pass it back to the Python interpreter. This includes calls to
the error setting functions such as ``PyErr_SetString``.

.. code-block:: cpp

    PyErr_SetString(PyExc_TypeError, "C API type error demo");
    throw py::error_already_set();

    // But it would be easier to simply...
    throw py::type_error("pybind11 wrapper type error");

Alternately, to ignore the error, call `PyErr_Clear
<https://docs.python.org/3/c-api/exceptions.html#c.PyErr_Clear>`_.

Any Python error must be thrown or cleared, or Python/pybind11 will be left in
an invalid state.

.. _unraisable_exceptions:

Handling unraisable exceptions
==============================

If a Python function invoked from a C++ destructor or any function marked
``noexcept(true)`` (collectively, "noexcept functions") throws an exception, there
is no way to propagate the exception, as such functions may not throw.
Should they throw or fail to catch any exceptions in their call graph,
the C++ runtime calls ``std::terminate()`` to abort immediately.

Similarly, Python exceptions raised in a class's ``__del__`` method do not
propagate, but are logged by Python as an unraisable error. In Python 3.8+, a
`system hook is triggered
<https://docs.python.org/3/library/sys.html#sys.unraisablehook>`_
and an auditing event is logged.

Any noexcept function should have a try-catch block that traps
class:`error_already_set` (or any other exception that can occur). Note that
pybind11 wrappers around Python exceptions such as
:class:`pybind11::value_error` are *not* Python exceptions; they are C++
exceptions that pybind11 catches and converts to Python exceptions. Noexcept
functions cannot propagate these exceptions either. A useful approach is to
convert them to Python exceptions and then ``discard_as_unraisable`` as shown
below.

.. code-block:: cpp

    void nonthrowing_func() noexcept(true) {
        try {
            // ...
        } catch (py::error_already_set &eas) {
            // Discard the Python error using Python APIs, using the C++ magic
            // variable __func__. Python already knows the type and value and of the
            // exception object.
            eas.discard_as_unraisable(__func__);
        } catch (const std::exception &e) {
            // Log and discard C++ exceptions.
            third_party::log(e);
        }
    }

.. versionadded:: 2.6