Custom type casters#
Some applications may prefer custom type casters that convert between existing
Python types and C++ types, similar to the list
↔ std::vector
and dict
↔ std::map
conversions which are built into pybind11.
Implementing custom type casters is fairly advanced usage.
While it is recommended to use the pybind11 API as much as possible, more complex examples may
require familiarity with the intricacies of the Python C API.
You can refer to the Python/C API Reference Manual
for more information.
The following snippets demonstrate how this works for a very simple Point2D
type.
We want this type to be convertible to C++ from Python types implementing the
Sequence
protocol and having two elements of type float
.
When returned from C++ to Python, it should be converted to a Python tuple[float, float]
.
For this type we could provide Python bindings for different arithmetic functions implemented
in C++ (here demonstrated by a simple negate
function).
namespace user_space {
struct Point2D {
double x;
double y;
};
Point2D negate(const Point2D &point) { return Point2D{-point.x, -point.y}; }
} // namespace user_space
The following Python snippet demonstrates the intended usage of negate
from the Python side:
from my_math_module import docs_advanced_cast_custom as m
point1 = [1.0, -1.0]
point2 = m.negate(point1)
assert point2 == (-1.0, 1.0)
To register the necessary conversion routines, it is necessary to add an
instantiation of the pybind11::detail::type_caster<T>
template.
Although this is an implementation detail, adding an instantiation of this
type is explicitly allowed.
namespace pybind11 {
namespace detail {
template <>
struct type_caster<user_space::Point2D> {
// This macro inserts a lot of boilerplate code and sets the type hint.
// `io_name` is used to specify different type hints for arguments and return values.
// The signature of our negate function would then look like:
// `negate(Sequence[float]) -> tuple[float, float]`
PYBIND11_TYPE_CASTER(user_space::Point2D, io_name("Sequence[float]", "tuple[float, float]"));
// C++ -> Python: convert `Point2D` to `tuple[float, float]`. The second and third arguments
// are used to indicate the return value policy and parent object (for
// return_value_policy::reference_internal) and are often ignored by custom casters.
// The return value should reflect the type hint specified by the second argument of `io_name`.
static handle
cast(const user_space::Point2D &number, return_value_policy /*policy*/, handle /*parent*/) {
return py::make_tuple(number.x, number.y).release();
}
// Python -> C++: convert a `PyObject` into a `Point2D` and return false upon failure. The
// second argument indicates whether implicit conversions should be allowed.
// The accepted types should reflect the type hint specified by the first argument of
// `io_name`.
bool load(handle src, bool /*convert*/) {
// Check if handle is a Sequence
if (!py::isinstance<py::sequence>(src)) {
return false;
}
auto seq = py::reinterpret_borrow<py::sequence>(src);
// Check if exactly two values are in the Sequence
if (seq.size() != 2) {
return false;
}
// Check if each element is either a float or an int
for (auto item : seq) {
if (!py::isinstance<py::float_>(item) && !py::isinstance<py::int_>(item)) {
return false;
}
}
value.x = seq[0].cast<double>();
value.y = seq[1].cast<double>();
return true;
}
};
} // namespace detail
} // namespace pybind11
// Bind the negate function
PYBIND11_MODULE(docs_advanced_cast_custom, m) { m.def("negate", user_space::negate); }
Note
A type_caster<T>
defined with PYBIND11_TYPE_CASTER(T, ...)
requires
that T
is default-constructible (value
is first default constructed
and then load()
assigns to it).
Note
For further information on the return_value_policy
argument of cast
refer to Return value policies.
To learn about the convert
argument of load
see Non-converting arguments.
Warning
When using custom type casters, it’s important to declare them consistently in every compilation unit of the Python extension module to satisfy the C++ One Definition Rule (ODR). Otherwise, undefined behavior can ensue.
Note
Using the type hint Sequence[float]
signals to static type checkers, that not only tuples may be
passed, but any type implementing the Sequence protocol, e.g., list[float]
.
Unfortunately, that loses the length information tuple[float, float]
provides.
One way of still providing some length information in type hints is using typing.Annotated
, e.g.,
Annotated[Sequence[float], 2]
, or further add libraries like
annotated-types.