Skip to content
Back to top
View this page

# 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, py::mod_gil_not_used()) { 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.