mirror of
https://github.com/pybind/pybind11.git
synced 2026-03-14 20:27:47 +00:00
* Apply smart_holder-branch-based PR #5280 on top of master. * Add pytest.skip("GraalPy does not raise UnicodeDecodeError") * Add `parent_scope` as first argument to `py::native_enum` ctor. * Replace `operator+=` API with `.finalize()` API. The error messages still need cleanup. * Resolve clang-tidy performance-unnecessary-value-param errors * Rename (effectively) native_enum_add_to_parent() -> finalize() * Update error message: pybind11::native_enum<...>("Fake", ...): MISSING .finalize() * Pass py::module_ by reference to resolve clang-tidy errors (this is entirely inconsequential otherwise for all practical purposes). * test_native_enum_correct_use_failure -> test_native_enum_missing_finalize_failure * Add test_native_enum_double_finalize(), test_native_enum_value_after_finalize() * Clean up public/protected API. * [ci skip] Update the Enumerations section in classes.rst * Rename `py::native_enum_kind` → `py::enum_kind` as suggested by gh-henryiii: https://github.com/pybind/pybind11/pull/5555#issuecomment-2711672335 * Experiment: StrEnum enum.StrEnum does not map to C++ enum: * https://chatgpt.com/share/67d5e965-ccb0-8008-95b7-0df2502309b3 ``` ============================= test session starts ============================== platform linux -- Python 3.12.3, pytest-8.3.3, pluggy-1.5.0 C++ Info: 13.3.0 C++20 __pybind11_internals_v10000000_system_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_1__ PYBIND11_SIMPLE_GIL_MANAGEMENT=False PYBIND11_NUMPY_1_ONLY=False configfile: pytest.ini plugins: parallel-0.1.1, xdist-3.6.1 collected 40 items / 39 deselected / 1 selected test_native_enum.py F [100%] =================================== FAILURES =================================== ________________________ test_native_enum_StrEnum_greek ________________________ def test_native_enum_StrEnum_greek(): assert not hasattr(m, "greek") > m.native_enum_StrEnum_greek(m) test_native_enum.py:150: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /usr/lib/python3.12/enum.py:764: in __call__ return cls._create_( boundary = None cls = <enum 'StrEnum'> module = None names = [('Alpha', 10), ('Omega', 20)] qualname = None start = 1 type = None value = 'greek' values = () /usr/lib/python3.12/enum.py:917: in _create_ return metacls.__new__(metacls, class_name, bases, classdict, boundary=boundary) _ = <class 'str'> bases = (<enum 'StrEnum'>,) boundary = None class_name = 'greek' classdict = {'_generate_next_value_': <function StrEnum._generate_next_value_ at 0x701ec1711e40>, 'Alpha': 10, 'Omega': 20, '__module__': 'test_native_enum'} cls = <enum 'StrEnum'> first_enum = <enum 'StrEnum'> item = ('Omega', 20) member_name = 'Omega' member_value = 20 metacls = <class 'enum.EnumType'> module = 'test_native_enum' names = [('Alpha', 10), ('Omega', 20)] qualname = None start = 1 type = None /usr/lib/python3.12/enum.py:606: in __new__ raise exc.with_traceback(tb) __class__ = <class 'enum.EnumType'> __new__ = <function StrEnum.__new__ at 0x701ec1711da0> _gnv = <staticmethod(<function StrEnum._generate_next_value_ at 0x701ec1711e40>)> _order_ = None _simple = False bases = (<enum 'StrEnum'>,) boundary = None classdict = {'Alpha': <enum._proto_member object at 0x701ebc74f9b0>, 'Omega': <enum._proto_member object at 0x701ebc74cce0>, '__module__': 'test_native_enum', '_all_bits_': 0, ...} cls = 'greek' exc = TypeError('10 is not a string') first_enum = <enum 'StrEnum'> ignore = ['_ignore_'] invalid_names = set() key = '_ignore_' kwds = {} member_names = {'Alpha': None, 'Omega': None} member_type = <class 'str'> metacls = <class 'enum.EnumType'> name = 'Omega' save_new = False tb = <traceback object at 0x701ebc7a6cc0> use_args = True value = 20 /usr/lib/python3.12/enum.py:596: in __new__ enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) __class__ = <class 'enum.EnumType'> __new__ = <function StrEnum.__new__ at 0x701ec1711da0> _gnv = <staticmethod(<function StrEnum._generate_next_value_ at 0x701ec1711e40>)> _order_ = None _simple = False bases = (<enum 'StrEnum'>,) boundary = None classdict = {'Alpha': <enum._proto_member object at 0x701ebc74f9b0>, 'Omega': <enum._proto_member object at 0x701ebc74cce0>, '__module__': 'test_native_enum', '_all_bits_': 0, ...} cls = 'greek' exc = TypeError('10 is not a string') first_enum = <enum 'StrEnum'> ignore = ['_ignore_'] invalid_names = set() key = '_ignore_' kwds = {} member_names = {'Alpha': None, 'Omega': None} member_type = <class 'str'> metacls = <class 'enum.EnumType'> name = 'Omega' save_new = False tb = <traceback object at 0x701ebc7a6cc0> use_args = True value = 20 /usr/lib/python3.12/enum.py:271: in __set_name__ enum_member = enum_class._new_member_(enum_class, *args) args = (10,) enum_class = <enum 'greek'> member_name = 'Alpha' self = <enum._proto_member object at 0x701ebc74f9b0> value = 10 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ cls = <enum 'greek'>, values = (10,) def __new__(cls, *values): "values must already be of type `str`" if len(values) > 3: raise TypeError('too many arguments for str(): %r' % (values, )) if len(values) == 1: # it must be a string if not isinstance(values[0], str): > raise TypeError('%r is not a string' % (values[0], )) E TypeError: 10 is not a string cls = <enum 'greek'> values = (10,) /usr/lib/python3.12/enum.py:1322: TypeError =========================== short test summary info ============================ FAILED test_native_enum.py::test_native_enum_StrEnum_greek - TypeError: 10 is... ======================= 1 failed, 39 deselected in 0.07s ======================= ERROR: completed_process.returncode=1 ``` * Remove StrEnum code. * Make enum_kind::Enum the default kind. * Catch redundant .export_values() calls. * [ci skip] Add back original documentation for `py::enum_` under new advanced/deprecated.rst * [ci skip] Add documentation for `py::enum_kind` and `py::detail::type_caster_enum_type_enabled` * Rename `Type` to `EnumType` for readability. * Eliminate py::enum_kind, use "enum.Enum", "enum.IntEnum" directly. This is still WIP. * EXPERIMENTAL StrEnum code. To be removed. * Remove experimental StrEnum code: My judgement: Supporting StrEnum is maybe nice, but not very valuable. I don't think it is worth the extra C++ code. A level of indirection would need to be managed, e.g. RED ↔ Python "r" ↔ C++ 0 Green ↔ Python "g" ↔ C++ 1 These mappings would need to be stored and processed. * Add test with enum.IntFlag (no production code changes required). * First import_or_getattr() implementation (dedicated tests are still missing). * Fix import_or_getattr() implementation, add tests, fix clang-tidy errors. * [ci skip] Update classes.rst: replace `py::enum_kind` with `native_type_name` * For "constructor similar to that of enum.Enum" point to https://docs.python.org/3/howto/enum.html#functional-api, as suggested by gh-timohl (https://github.com/pybind/pybind11/pull/5555#discussion_r2009277507). * Advertise Enum, IntEnum, Flag, IntFlags are compatible stdlib enum types in the documentation (as suggested by gh-timohl, https://github.com/pybind/pybind11/pull/5555#pullrequestreview-2708832587); add test for enum.Flag to ensure that is actually true.
138 lines
5.7 KiB
ReStructuredText
138 lines
5.7 KiB
ReStructuredText
.. _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) { 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>`_.
|