Skip to content

Commit

Permalink
Updated code in adcanced/cast example and improved documentation text
Browse files Browse the repository at this point in the history
  • Loading branch information
timohl committed Dec 1, 2024
1 parent 1fe101a commit 562153d
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 40 deletions.
65 changes: 27 additions & 38 deletions docs/advanced/cast/custom.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ 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 and requires
familiarity with the intricacies of the Python C API.
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.

Expand Down Expand Up @@ -64,57 +65,41 @@ type is explicitly allowed.
PYBIND11_TYPE_CASTER(user_space::Point2D, const_name("tuple"));
// `arg_name` and `return_name` may optionally be used to specify type hints separately for
// arguments and return values.
// The signature of our identity function would then look like:
// `identity(Sequence[float]) -> tuple[float, float]`
// The signature of our negate function would then look like:
// `negate(Sequence[float]) -> tuple[float, float]`
static constexpr auto arg_name = const_name("Sequence[float]");
static constexpr auto return_name = const_name("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.
static handle cast(const user_space::Point2D &number, return_value_policy, handle) {
// Convert x and y components to python float
auto *x = PyFloat_FromDouble(number.x);
auto *y = PyFloat_FromDouble(number.y);
// Check if conversion was successful otherwise clean up references and return null
if (!x || !y) {
Py_XDECREF(x);
Py_XDECREF(y);
return nullptr;
}
// Create tuple from x and y
auto *t = PyTuple_Pack(2, x, y);
// Decrement references (the tuple now owns x an y)
Py_DECREF(x);
Py_DECREF(y);
return t;
// The return value should reflect the type hint specified by `return_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.
bool load(handle src, bool) {
// Check if handle is valid Sequence of length 2
if (!src || PySequence_Check(src.ptr()) == 0 || PySequence_Length(src.ptr()) != 2) {
// The accepted types should reflect the type hint specified by `arg_name`.
bool load(handle src, bool /*convert*/) {
// Check if handle is a Sequence
if (!py::isinstance<py::sequence>(src)) {
return false;
}
auto *x = PySequence_GetItem(src.ptr(), 0);
auto *y = PySequence_GetItem(src.ptr(), 1);
// Check if values are float or int (both are allowed with float as type hint)
if (!x || !(PyFloat_Check(x) || PyLong_Check(x)) || !y
|| !(PyFloat_Check(y) || PyLong_Check(y))) {
Py_XDECREF(x);
Py_XDECREF(y);
auto seq = py::reinterpret_borrow<py::sequence>(src);
// Check if exactly two values are in the Sequence
if (seq.size() != 2) {
return false;
}
// value is a default constructed Point2D
value.x = PyFloat_AsDouble(x);
value.y = PyFloat_AsDouble(y);
Py_DECREF(x);
Py_DECREF(y);
if ((value.x == -1.0 || value.y == -1.0) && PyErr_Occurred()) {
PyErr_Clear();
return false;
// Check if each element is either a float or an int
for (auto item : seq) {
if (!py::isinstance<py::float_>(item) and !py::isinstance<py::int_>(item)) {
return false;
}
}
value.x = seq[0].cast<double>();
value.y = seq[1].cast<double>();
return true;
}
};
Expand All @@ -131,6 +116,10 @@ type is explicitly allowed.
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:`non-converting-arguments`.

.. warning::

When using custom type casters, it's important to declare them consistently
Expand Down
6 changes: 4 additions & 2 deletions tests/test_docs_advanced_cast_custom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,23 @@ struct type_caster<user_space::Point2D> {
PYBIND11_TYPE_CASTER(user_space::Point2D, const_name("tuple"));
// `arg_name` and `return_name` may optionally be used to specify type hints separately for
// arguments and return values.
// The signature of our identity function would then look like:
// `identity(Sequence[float]) -> tuple[float, float]`
// The signature of our negate function would then look like:
// `negate(Sequence[float]) -> tuple[float, float]`
static constexpr auto arg_name = const_name("Sequence[float]");
static constexpr auto return_name = const_name("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 `return_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 `arg_name`.
bool load(handle src, bool /*convert*/) {
// Check if handle is a Sequence
if (!py::isinstance<py::sequence>(src)) {
Expand Down

0 comments on commit 562153d

Please sign in to comment.