11Custom type casters
22===================
33
4- In very rare cases, applications may require custom type casters that cannot be
5- expressed using the abstractions provided by pybind11, thus requiring raw
6- Python C API calls. This is fairly advanced usage and should only be pursued by
7- experts who are familiar with the intricacies of Python reference counting.
8-
9- The following snippets demonstrate how this works for a very simple ``inty ``
10- type that that should be convertible from Python types that provide a
11- ``__int__(self) `` method.
4+ Some applications may prefer custom type casters that convert between existing
5+ Python types and C++ types, similar to the ``list `` ↔ ``std::vector ``
6+ and ``dict `` ↔ ``std::map `` conversions which are built into pybind11.
7+ Implementing custom type casters is fairly advanced usage.
8+ While it is recommended to use the pybind11 API as much as possible, more complex examples may
9+ require familiarity with the intricacies of the Python C API.
10+ You can refer to the `Python/C API Reference Manual <https://docs.python.org/3/c-api/index.html >`_
11+ for more information.
12+
13+ The following snippets demonstrate how this works for a very simple ``Point2D `` type.
14+ We want this type to be convertible to C++ from Python types implementing the
15+ ``Sequence `` protocol and having two elements of type ``float ``.
16+ When returned from C++ to Python, it should be converted to a Python ``tuple[float, float] ``.
17+ For this type we could provide Python bindings for different arithmetic functions implemented
18+ in C++ (here demonstrated by a simple ``negate `` function).
19+
20+ ..
21+ PLEASE KEEP THE CODE BLOCKS IN SYNC WITH
22+ tests/test_docs_advanced_cast_custom.cpp
23+ tests/test_docs_advanced_cast_custom.py
24+ Ideally, change the test, run pre-commit (incl. clang-format),
25+ then copy the changed code back here.
26+ Also use TEST_SUBMODULE in tests, but PYBIND11_MODULE in docs.
1227
1328.. code-block :: cpp
1429
15- struct inty { long long_value; };
30+ namespace user_space {
1631
17- void print(inty s) {
18- std::cout << s.long_value << std::endl;
19- }
32+ struct Point2D {
33+ double x;
34+ double y;
35+ };
2036
21- The following Python snippet demonstrates the intended usage from the Python side:
37+ Point2D negate(const Point2D &point) { return Point2D{-point.x, -point.y}; }
2238
23- .. code-block :: python
39+ } // namespace user_space
2440
25- class A :
26- def __int__ (self ):
27- return 123
2841
42+ The following Python snippet demonstrates the intended usage of ``negate `` from the Python side:
43+
44+ .. code-block :: python
2945
30- from example import print
46+ from my_math_module import docs_advanced_cast_custom as m
3147
32- print (A())
48+ point1 = [1.0 , - 1.0 ]
49+ point2 = m.negate(point1)
50+ assert point2 == (- 1.0 , 1.0 )
3351
3452 To register the necessary conversion routines, it is necessary to add an
3553instantiation of the ``pybind11::detail::type_caster<T> `` template.
@@ -38,56 +56,82 @@ type is explicitly allowed.
3856
3957.. code-block :: cpp
4058
41- namespace PYBIND11_NAMESPACE { namespace detail {
42- template <> struct type_caster<inty> {
43- public:
44- /**
45- * This macro establishes the name 'inty' in
46- * function signatures and declares a local variable
47- * 'value' of type inty
48- */
49- PYBIND11_TYPE_CASTER(inty, const_name("inty"));
50-
51- /**
52- * Conversion part 1 (Python->C++): convert a PyObject into a inty
53- * instance or return false upon failure. The second argument
54- * indicates whether implicit conversions should be applied.
55- */
56- bool load(handle src, bool) {
57- /* Extract PyObject from handle */
58- PyObject *source = src.ptr();
59- /* Try converting into a Python integer value */
60- PyObject *tmp = PyNumber_Long(source);
61- if (!tmp)
59+ namespace pybind11 {
60+ namespace detail {
61+
62+ template <>
63+ struct type_caster<user_space::Point2D> {
64+ // This macro inserts a lot of boilerplate code and sets the default type hint to `tuple`
65+ PYBIND11_TYPE_CASTER(user_space::Point2D, const_name("tuple"));
66+ // `arg_name` and `return_name` may optionally be used to specify type hints separately for
67+ // arguments and return values.
68+ // The signature of our negate function would then look like:
69+ // `negate(Sequence[float]) -> tuple[float, float]`
70+ static constexpr auto arg_name = const_name("Sequence[float]");
71+ static constexpr auto return_name = const_name("tuple[float, float]");
72+
73+ // C++ -> Python: convert `Point2D` to `tuple[float, float]`. The second and third arguments
74+ // are used to indicate the return value policy and parent object (for
75+ // return_value_policy::reference_internal) and are often ignored by custom casters.
76+ // The return value should reflect the type hint specified by `return_name`.
77+ static handle
78+ cast(const user_space::Point2D &number, return_value_policy /*policy*/, handle /*parent*/) {
79+ return py::make_tuple(number.x, number.y).release();
80+ }
81+
82+ // Python -> C++: convert a `PyObject` into a `Point2D` and return false upon failure. The
83+ // second argument indicates whether implicit conversions should be allowed.
84+ // The accepted types should reflect the type hint specified by `arg_name`.
85+ bool load(handle src, bool /*convert*/) {
86+ // Check if handle is a Sequence
87+ if (!py::isinstance<py::sequence>(src)) {
88+ return false;
89+ }
90+ auto seq = py::reinterpret_borrow<py::sequence>(src);
91+ // Check if exactly two values are in the Sequence
92+ if (seq.size() != 2) {
93+ return false;
94+ }
95+ // Check if each element is either a float or an int
96+ for (auto item : seq) {
97+ if (!py::isinstance<py::float_>(item) && !py::isinstance<py::int_>(item)) {
6298 return false;
63- /* Now try to convert into a C++ int */
64- value.long_value = PyLong_AsLong(tmp);
65- Py_DECREF(tmp);
66- /* Ensure return code was OK (to avoid out-of-range errors etc) */
67- return !(value.long_value == -1 && !PyErr_Occurred());
99+ }
68100 }
101+ value.x = seq[0].cast<double>();
102+ value.y = seq[1].cast<double>();
103+ return true;
104+ }
105+ };
69106
70- /**
71- * Conversion part 2 (C++ -> Python): convert an inty instance into
72- * a Python object. The second and third arguments are used to
73- * indicate the return value policy and parent object (for
74- * ``return_value_policy::reference_internal``) and are generally
75- * ignored by implicit casters.
76- */
77- static handle cast(inty src, return_value_policy /* policy */, handle /* parent */) {
78- return PyLong_FromLong(src.long_value);
79- }
80- };
81- }} // namespace PYBIND11_NAMESPACE::detail
107+ } // namespace detail
108+ } // namespace pybind11
109+
110+ // Bind the negate function
111+ PYBIND11_MODULE(docs_advanced_cast_custom, m) { m.def("negate", user_space::negate); }
82112
83113 .. note ::
84114
85115 A ``type_caster<T> `` defined with ``PYBIND11_TYPE_CASTER(T, ...) `` requires
86116 that ``T `` is default-constructible (``value `` is first default constructed
87117 and then ``load() `` assigns to it).
88118
119+ .. note ::
120+ For further information on the ``return_value_policy `` argument of ``cast `` refer to :ref: `return_value_policies `.
121+ To learn about the ``convert `` argument of ``load `` see :ref: `nonconverting_arguments `.
122+
89123.. warning ::
90124
91125 When using custom type casters, it's important to declare them consistently
92- in every compilation unit of the Python extension module. Otherwise,
126+ in every compilation unit of the Python extension module to satisfy the C++ One Definition Rule
127+ (`ODR <https://en.cppreference.com/w/cpp/language/definition >`_). Otherwise,
93128 undefined behavior can ensue.
129+
130+ .. note ::
131+
132+ Using the type hint ``Sequence[float] `` signals to static type checkers, that not only tuples may be
133+ passed, but any type implementing the Sequence protocol, e.g., ``list[float] ``.
134+ Unfortunately, that loses the length information ``tuple[float, float] `` provides.
135+ One way of still providing some length information in type hints is using ``typing.Annotated ``, e.g.,
136+ ``Annotated[Sequence[float], 2] ``, or further add libraries like
137+ `annotated-types <https://github.com/annotated-types/annotated-types >`_.
0 commit comments