Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pybind11
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
open
pybind11
Commits
5a7d17ff
Commit
5a7d17ff
authored
Jun 17, 2016
by
Pim Schellart
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add support for user defined exception translators
parent
678d59d2
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
302 additions
and
12 deletions
+302
-12
docs/advanced.rst
+61
-0
example/CMakeLists.txt
+1
-0
example/example.cpp
+2
-0
example/example19.cpp
+108
-0
example/example19.py
+42
-0
example/example19.ref
+15
-0
include/pybind11/cast.h
+21
-0
include/pybind11/common.h
+2
-0
include/pybind11/pybind11.h
+50
-12
No files found.
docs/advanced.rst
View file @
5a7d17ff
...
@@ -817,6 +817,8 @@ In other words, :func:`init` creates an anonymous function that invokes an
...
@@ -817,6 +817,8 @@ In other words, :func:`init` creates an anonymous function that invokes an
in-place constructor. Memory allocation etc. is already take care of beforehand
in-place constructor. Memory allocation etc. is already take care of beforehand
within pybind11.
within pybind11.
.. _catching_and_throwing_exceptions:
Catching and throwing exceptions
Catching and throwing exceptions
================================
================================
...
@@ -869,6 +871,65 @@ There is also a special exception :class:`cast_error` that is thrown by
...
@@ -869,6 +871,65 @@ 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
:func:`handle::call` when the input arguments cannot be converted to Python
objects.
objects.
Registering custom exception translators
========================================
If the default exception conversion policy described
:ref:`above <catching_and_throwing_exceptions>`
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:`example19.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``:
.. code-block:: cpp
py::register_exception_translator([](std::exception_ptr p) {
try {
if (p) std::rethrow_exception(p);
} catch (const MyCustomException &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.
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.
.. 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``.
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 ``py::base``.
.. _opaque:
.. _opaque:
Treating STL data structures as opaque objects
Treating STL data structures as opaque objects
...
...
example/CMakeLists.txt
View file @
5a7d17ff
...
@@ -25,6 +25,7 @@ set(PYBIND11_EXAMPLES
...
@@ -25,6 +25,7 @@ set(PYBIND11_EXAMPLES
example16.cpp
example16.cpp
example17.cpp
example17.cpp
example18.cpp
example18.cpp
example19.cpp
issues.cpp
issues.cpp
)
)
...
...
example/example.cpp
View file @
5a7d17ff
...
@@ -27,6 +27,7 @@ void init_ex15(py::module &);
...
@@ -27,6 +27,7 @@ void init_ex15(py::module &);
void
init_ex16
(
py
::
module
&
);
void
init_ex16
(
py
::
module
&
);
void
init_ex17
(
py
::
module
&
);
void
init_ex17
(
py
::
module
&
);
void
init_ex18
(
py
::
module
&
);
void
init_ex18
(
py
::
module
&
);
void
init_ex19
(
py
::
module
&
);
void
init_issues
(
py
::
module
&
);
void
init_issues
(
py
::
module
&
);
#if defined(PYBIND11_TEST_EIGEN)
#if defined(PYBIND11_TEST_EIGEN)
...
@@ -54,6 +55,7 @@ PYBIND11_PLUGIN(example) {
...
@@ -54,6 +55,7 @@ PYBIND11_PLUGIN(example) {
init_ex16
(
m
);
init_ex16
(
m
);
init_ex17
(
m
);
init_ex17
(
m
);
init_ex18
(
m
);
init_ex18
(
m
);
init_ex19
(
m
);
init_issues
(
m
);
init_issues
(
m
);
#if defined(PYBIND11_TEST_EIGEN)
#if defined(PYBIND11_TEST_EIGEN)
...
...
example/example19.cpp
0 → 100644
View file @
5a7d17ff
/*
example/example19.cpp -- exception translation
Copyright (c) 2016 Pim Schellart <P.Schellart@princeton.edu>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "example.h"
// A type that should be raised as an exeption in Python
class
MyException
:
public
std
::
exception
{
public
:
explicit
MyException
(
const
char
*
m
)
:
message
{
m
}
{}
virtual
const
char
*
what
()
const
noexcept
override
{
return
message
.
c_str
();}
private
:
std
::
string
message
=
""
;
};
// A type that should be translated to a standard Python exception
class
MyException2
:
public
std
::
exception
{
public
:
explicit
MyException2
(
const
char
*
m
)
:
message
{
m
}
{}
virtual
const
char
*
what
()
const
noexcept
override
{
return
message
.
c_str
();}
private
:
std
::
string
message
=
""
;
};
// A type that is not derived from std::exception (and is thus unknown)
class
MyException3
{
public
:
explicit
MyException3
(
const
char
*
m
)
:
message
{
m
}
{}
virtual
const
char
*
what
()
const
noexcept
{
return
message
.
c_str
();}
private
:
std
::
string
message
=
""
;
};
// A type that should be translated to MyException
// and delegated to its exception translator
class
MyException4
:
public
std
::
exception
{
public
:
explicit
MyException4
(
const
char
*
m
)
:
message
{
m
}
{}
virtual
const
char
*
what
()
const
noexcept
override
{
return
message
.
c_str
();}
private
:
std
::
string
message
=
""
;
};
void
throws1
()
{
throw
MyException
(
"this error should go to a custom type"
);
}
void
throws2
()
{
throw
MyException2
(
"this error should go to a standard Python exception"
);
}
void
throws3
()
{
throw
MyException3
(
"this error cannot be translated"
);
}
void
throws4
()
{
throw
MyException4
(
"this error is rethrown"
);
}
void
throws_logic_error
()
{
throw
std
::
logic_error
(
"this error should fall through to the standard handler"
);
}
void
init_ex19
(
py
::
module
&
m
)
{
// make a new custom exception and use it as a translation target
static
py
::
exception
<
MyException
>
ex
(
m
,
"MyException"
);
py
::
register_exception_translator
([](
std
::
exception_ptr
p
)
{
try
{
if
(
p
)
std
::
rethrow_exception
(
p
);
}
catch
(
const
MyException
&
e
)
{
PyErr_SetString
(
ex
.
ptr
(),
e
.
what
());
}
});
// register new translator for MyException2
// no need to store anything here because this type will
// never by visible from Python
py
::
register_exception_translator
([](
std
::
exception_ptr
p
)
{
try
{
if
(
p
)
std
::
rethrow_exception
(
p
);
}
catch
(
const
MyException2
&
e
)
{
PyErr_SetString
(
PyExc_RuntimeError
,
e
.
what
());
}
});
// register new translator for MyException4
// which will catch it and delegate to the previously registered
// translator for MyException by throwing a new exception
py
::
register_exception_translator
([](
std
::
exception_ptr
p
)
{
try
{
if
(
p
)
std
::
rethrow_exception
(
p
);
}
catch
(
const
MyException4
&
e
)
{
throw
MyException
(
e
.
what
());
}
});
m
.
def
(
"throws1"
,
&
throws1
);
m
.
def
(
"throws2"
,
&
throws2
);
m
.
def
(
"throws3"
,
&
throws3
);
m
.
def
(
"throws4"
,
&
throws4
);
m
.
def
(
"throws_logic_error"
,
&
throws_logic_error
);
}
example/example19.py
0 → 100644
View file @
5a7d17ff
#!/usr/bin/env python
from
__future__
import
print_function
import
sys
sys
.
path
.
append
(
'.'
)
import
example
print
(
"Can we catch a MyException?"
)
try
:
example
.
throws1
()
except
example
.
MyException
as
e
:
print
(
e
.
__class__
.
__name__
,
":"
,
e
)
print
(
""
)
print
(
"Can we translate to standard Python exceptions?"
)
try
:
example
.
throws2
()
except
Exception
as
e
:
print
(
e
.
__class__
.
__name__
,
":"
,
e
)
print
(
""
)
print
(
"Can we handle unknown exceptions?"
)
try
:
example
.
throws3
()
except
Exception
as
e
:
print
(
e
.
__class__
.
__name__
,
":"
,
e
)
print
(
""
)
print
(
"Can we delegate to another handler by rethrowing?"
)
try
:
example
.
throws4
()
except
example
.
MyException
as
e
:
print
(
e
.
__class__
.
__name__
,
":"
,
e
)
print
(
""
)
print
(
"Can we fall-through to the default handler?"
)
try
:
example
.
throws_logic_error
()
except
Exception
as
e
:
print
(
e
.
__class__
.
__name__
,
":"
,
e
)
print
(
""
)
example/example19.ref
0 → 100644
View file @
5a7d17ff
Can we catch a MyException?
MyException : this error should go to a custom type
Can we translate to standard Python exceptions?
RuntimeError : this error should go to a standard Python exception
Can we handle unknown exceptions?
RuntimeError : Caught an unknown exception!
Can we delegate to another handler by rethrowing?
MyException : this error is rethrown
Can we fall-through to the default handler?
RuntimeError : this error should fall through to the standard handler
include/pybind11/cast.h
View file @
5a7d17ff
...
@@ -49,6 +49,27 @@ PYBIND11_NOINLINE inline internals &get_internals() {
...
@@ -49,6 +49,27 @@ PYBIND11_NOINLINE inline internals &get_internals() {
internals_ptr
->
istate
=
tstate
->
interp
;
internals_ptr
->
istate
=
tstate
->
interp
;
#endif
#endif
builtins
[
id
]
=
capsule
(
internals_ptr
);
builtins
[
id
]
=
capsule
(
internals_ptr
);
internals_ptr
->
registered_exception_translators
.
push_front
(
[](
std
::
exception_ptr
p
)
->
void
{
try
{
if
(
p
)
std
::
rethrow_exception
(
p
);
}
catch
(
const
error_already_set
&
)
{
return
;
}
catch
(
const
index_error
&
e
)
{
PyErr_SetString
(
PyExc_IndexError
,
e
.
what
());
return
;
}
catch
(
const
value_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
;
}
catch
(
const
stop_iteration
&
e
)
{
PyErr_SetString
(
PyExc_StopIteration
,
e
.
what
());
return
;
}
catch
(
const
std
::
bad_alloc
&
e
)
{
PyErr_SetString
(
PyExc_MemoryError
,
e
.
what
());
return
;
}
catch
(
const
std
::
domain_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
;
}
catch
(
const
std
::
invalid_argument
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
;
}
catch
(
const
std
::
length_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
;
}
catch
(
const
std
::
out_of_range
&
e
)
{
PyErr_SetString
(
PyExc_IndexError
,
e
.
what
());
return
;
}
catch
(
const
std
::
range_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
;
}
catch
(
const
std
::
exception
&
e
)
{
PyErr_SetString
(
PyExc_RuntimeError
,
e
.
what
());
return
;
}
catch
(...)
{
PyErr_SetString
(
PyExc_RuntimeError
,
"Caught an unknown exception!"
);
return
;
}
}
);
}
}
return
*
internals_ptr
;
return
*
internals_ptr
;
}
}
...
...
include/pybind11/common.h
View file @
5a7d17ff
...
@@ -66,6 +66,7 @@
...
@@ -66,6 +66,7 @@
# pragma warning(pop)
# pragma warning(pop)
#endif
#endif
#include <forward_list>
#include <vector>
#include <vector>
#include <string>
#include <string>
#include <stdexcept>
#include <stdexcept>
...
@@ -264,6 +265,7 @@ struct internals {
...
@@ -264,6 +265,7 @@ struct internals {
std
::
unordered_map
<
const
void
*
,
void
*>
registered_types_py
;
// PyTypeObject* -> type_info
std
::
unordered_map
<
const
void
*
,
void
*>
registered_types_py
;
// PyTypeObject* -> type_info
std
::
unordered_map
<
const
void
*
,
void
*>
registered_instances
;
// void * -> PyObject*
std
::
unordered_map
<
const
void
*
,
void
*>
registered_instances
;
// void * -> PyObject*
std
::
unordered_set
<
std
::
pair
<
const
PyObject
*
,
const
char
*>
,
overload_hash
>
inactive_overload_cache
;
std
::
unordered_set
<
std
::
pair
<
const
PyObject
*
,
const
char
*>
,
overload_hash
>
inactive_overload_cache
;
std
::
forward_list
<
void
(
*
)
(
std
::
exception_ptr
)
>
registered_exception_translators
;
#if defined(WITH_THREAD)
#if defined(WITH_THREAD)
decltype
(
PyThread_create_key
())
tstate
=
0
;
// Usually an int but a long on Cygwin64 with Python 3.x
decltype
(
PyThread_create_key
())
tstate
=
0
;
// Usually an int but a long on Cygwin64 with Python 3.x
PyInterpreterState
*
istate
=
nullptr
;
PyInterpreterState
*
istate
=
nullptr
;
...
...
include/pybind11/pybind11.h
View file @
5a7d17ff
...
@@ -401,19 +401,32 @@ protected:
...
@@ -401,19 +401,32 @@ protected:
if
(
result
.
ptr
()
!=
PYBIND11_TRY_NEXT_OVERLOAD
)
if
(
result
.
ptr
()
!=
PYBIND11_TRY_NEXT_OVERLOAD
)
break
;
break
;
}
}
}
catch
(
const
error_already_set
&
)
{
return
nullptr
;
}
catch
(
const
error_already_set
&
)
{
}
catch
(
const
index_error
&
e
)
{
PyErr_SetString
(
PyExc_IndexError
,
e
.
what
());
return
nullptr
;
return
nullptr
;
}
catch
(
const
value_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
nullptr
;
}
catch
(
const
stop_iteration
&
e
)
{
PyErr_SetString
(
PyExc_StopIteration
,
e
.
what
());
return
nullptr
;
}
catch
(
const
std
::
bad_alloc
&
e
)
{
PyErr_SetString
(
PyExc_MemoryError
,
e
.
what
());
return
nullptr
;
}
catch
(
const
std
::
domain_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
nullptr
;
}
catch
(
const
std
::
invalid_argument
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
nullptr
;
}
catch
(
const
std
::
length_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
nullptr
;
}
catch
(
const
std
::
out_of_range
&
e
)
{
PyErr_SetString
(
PyExc_IndexError
,
e
.
what
());
return
nullptr
;
}
catch
(
const
std
::
range_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
nullptr
;
}
catch
(
const
std
::
exception
&
e
)
{
PyErr_SetString
(
PyExc_RuntimeError
,
e
.
what
());
return
nullptr
;
}
catch
(...)
{
}
catch
(...)
{
PyErr_SetString
(
PyExc_RuntimeError
,
"Caught an unknown exception!"
);
/* When an exception is caught, give each registered exception
translator a chance to translate it to a Python exception
in reverse order of registration.
A translator may choose to do one of the following:
- catch the exception and call PyErr_SetString or PyErr_SetObject
to set a standard (or custom) Python exception, or
- do nothing and let the exception fall through to the next translator, or
- delegate translation to the next translator by throwing a new type of exception. */
auto
last_exception
=
std
::
current_exception
();
auto
&
registered_exception_translators
=
pybind11
::
detail
::
get_internals
().
registered_exception_translators
;
for
(
auto
&
translator
:
registered_exception_translators
)
{
try
{
translator
(
last_exception
);
}
catch
(...)
{
last_exception
=
std
::
current_exception
();
continue
;
}
return
nullptr
;
}
PyErr_SetString
(
PyExc_SystemError
,
"Exception escaped from default exception translator!"
);
return
nullptr
;
return
nullptr
;
}
}
...
@@ -1089,6 +1102,31 @@ template <typename InputType, typename OutputType> void implicitly_convertible()
...
@@ -1089,6 +1102,31 @@ template <typename InputType, typename OutputType> void implicitly_convertible()
((
detail
::
type_info
*
)
it
->
second
)
->
implicit_conversions
.
push_back
(
implicit_caster
);
((
detail
::
type_info
*
)
it
->
second
)
->
implicit_conversions
.
push_back
(
implicit_caster
);
}
}
template
<
typename
ExceptionTranslator
>
void
register_exception_translator
(
ExceptionTranslator
&&
translator
)
{
detail
::
get_internals
().
registered_exception_translators
.
push_front
(
std
::
forward
<
ExceptionTranslator
>
(
translator
));
}
/* Wrapper to generate a new Python exception type.
*
* This should only be used with PyErr_SetString for now.
* It is not (yet) possible to use as a py::base.
* Template type argument is reserved for future use.
*/
template
<
typename
type
>
class
exception
:
public
object
{
public
:
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
());
m_ptr
=
PyErr_NewException
(
exception_name
,
base
,
NULL
);
inc_ref
();
// PyModule_AddObject() steals a reference
PyModule_AddObject
(
m
.
ptr
(),
name
.
c_str
(),
m_ptr
);
}
};
#if defined(WITH_THREAD)
#if defined(WITH_THREAD)
/* The functions below essentially reproduce the PyGILState_* API using a RAII
/* The functions below essentially reproduce the PyGILState_* API using a RAII
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment