1
1
Custom type casters
2
2
===================
3
3
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.
12
27
13
28
.. code-block :: cpp
14
29
15
- struct inty { long long_value; };
30
+ namespace user_space {
16
31
17
- void print(inty s) {
18
- std::cout << s.long_value << std::endl;
19
- }
32
+ struct Point2D {
33
+ double x;
34
+ double y;
35
+ };
20
36
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}; }
22
38
23
- .. code-block :: python
39
+ } // namespace user_space
24
40
25
- class A :
26
- def __int__ (self ):
27
- return 123
28
41
42
+ The following Python snippet demonstrates the intended usage of ``negate `` from the Python side:
43
+
44
+ .. code-block :: python
29
45
30
- from example import print
46
+ from my_math_module import docs_advanced_cast_custom as m
31
47
32
- print (A())
48
+ point1 = [1.0 , - 1.0 ]
49
+ point2 = m.negate(point1)
50
+ assert point2 == (- 1.0 , 1.0 )
33
51
34
52
To register the necessary conversion routines, it is necessary to add an
35
53
instantiation of the ``pybind11::detail::type_caster<T> `` template.
@@ -38,56 +56,82 @@ type is explicitly allowed.
38
56
39
57
.. code-block :: cpp
40
58
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)) {
62
98
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
+ }
68
100
}
101
+ value.x = seq[0].cast<double>();
102
+ value.y = seq[1].cast<double>();
103
+ return true;
104
+ }
105
+ };
69
106
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); }
82
112
83
113
.. note ::
84
114
85
115
A ``type_caster<T> `` defined with ``PYBIND11_TYPE_CASTER(T, ...) `` requires
86
116
that ``T `` is default-constructible (``value `` is first default constructed
87
117
and then ``load() `` assigns to it).
88
118
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
+
89
123
.. warning ::
90
124
91
125
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,
93
128
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