| .. _custom_type_caster: | |
| 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 <https://docs.python.org/3/c-api/index.html>`_ | |
| 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). | |
| .. | |
| PLEASE KEEP THE CODE BLOCKS IN SYNC WITH | |
| tests/test_docs_advanced_cast_custom.cpp | |
| tests/test_docs_advanced_cast_custom.py | |
| Ideally, change the test, run pre-commit (incl. clang-format), | |
| then copy the changed code back here. | |
| Also use TEST_SUBMODULE in tests, but PYBIND11_MODULE in docs. | |
| .. code-block:: cpp | |
| 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: | |
| .. code-block:: python | |
| 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. | |
| .. code-block:: cpp | |
| 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 :ref:`return_value_policies`. | |
| To learn about the ``convert`` argument of ``load`` see :ref:`nonconverting_arguments`. | |
| .. note:: | |
| If a custom ``load()`` produces a non-owning view of storage owned by a | |
| Python object, and keeping that object alive is sufficient to keep the | |
| storage valid, register the owner (often ``src``) with | |
| ``pybind11::detail::loader_life_support::try_add_patient(owner)``. During a | |
| bound-function call, this keeps the owner alive until the call returns. With | |
| no active call frame, it returns ``false`` and the caller remains | |
| responsible for the lifetime. | |
| If ``load()`` instead creates a temporary Python object to own the view's | |
| storage, use the strict ``loader_life_support::add_patient(owner)``. Outside | |
| a bound-function call, it raises :class:`cast_error` rather than allowing a | |
| dangling view. Neither form permits C++ code to retain the view after the | |
| call without separate lifetime management. These ``detail`` helpers are | |
| intended only for type-caster implementations. See | |
| :ref:`string_view_lifetime` for a concrete example. | |
| .. 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 <https://en.cppreference.com/w/cpp/language/definition>`_). 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 <https://github.com/annotated-types/annotated-types>`_. |