| .. _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`. |
| |
| .. 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>`_. |