Skip to content

Commit 4629fbc

Browse files
committed
Merge branch 'master' into sh_merge_master
2 parents 8edb638 + 5b503f7 commit 4629fbc

25 files changed

+694
-141
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,10 +365,6 @@ jobs:
365365
strategy:
366366
fail-fast: false
367367
matrix:
368-
clang:
369-
- dev
370-
std:
371-
- 11
372368
container_suffix:
373369
- ""
374370
include:

.github/workflows/emscripten.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
submodules: true
2424
fetch-depth: 0
2525

26-
- uses: pypa/cibuildwheel@v2.21
26+
- uses: pypa/cibuildwheel@v2.22
2727
env:
2828
PYODIDE_BUILD_EXPORTS: whole_archive
2929
with:

.github/workflows/pip.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ jobs:
104104
- uses: actions/download-artifact@v4
105105

106106
- name: Generate artifact attestation for sdist and wheel
107-
uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4
107+
uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0
108108
with:
109109
subject-path: "*/pybind11*"
110110

.pre-commit-config.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ repos:
2525

2626
# Clang format the codebase automatically
2727
- repo: https://github.com/pre-commit/mirrors-clang-format
28-
rev: "v19.1.3"
28+
rev: "v19.1.4"
2929
hooks:
3030
- id: clang-format
3131
types_or: [c++, c, cuda]
3232

3333
# Ruff, the Python auto-correcting linter/formatter written in Rust
3434
- repo: https://github.com/astral-sh/ruff-pre-commit
35-
rev: v0.7.2
35+
rev: v0.8.1
3636
hooks:
3737
- id: ruff
3838
args: ["--fix", "--show-fixes"]
@@ -95,7 +95,7 @@ repos:
9595

9696
# Avoid directional quotes
9797
- repo: https://github.com/sirosen/texthooks
98-
rev: "0.6.7"
98+
rev: "0.6.8"
9999
hooks:
100100
- id: fix-ligatures
101101
- id: fix-smartquotes
@@ -144,14 +144,14 @@ repos:
144144

145145
# PyLint has native support - not always usable, but works for us
146146
- repo: https://github.com/PyCQA/pylint
147-
rev: "v3.3.1"
147+
rev: "v3.3.2"
148148
hooks:
149149
- id: pylint
150150
files: ^pybind11
151151

152152
# Check schemas on some of our YAML files
153153
- repo: https://github.com/python-jsonschema/check-jsonschema
154-
rev: 0.29.4
154+
rev: 0.30.0
155155
hooks:
156156
- id: check-readthedocs
157157
- id: check-github-workflows

docs/advanced/cast/custom.rst

Lines changed: 102 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,53 @@
11
Custom 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
3553
instantiation 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>`_.

docs/advanced/cast/overview.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
151151
+------------------------------------+---------------------------+-----------------------------------+
152152
| ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/stl.h` |
153153
+------------------------------------+---------------------------+-----------------------------------+
154-
| ``std::filesystem::path<T>`` | STL path (C++17) [#]_ | :file:`pybind11/stl/filesystem.h` |
154+
| ``std::filesystem::path`` | STL path (C++17) [#]_ | :file:`pybind11/stl/filesystem.h` |
155155
+------------------------------------+---------------------------+-----------------------------------+
156156
| ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` |
157157
+------------------------------------+---------------------------+-----------------------------------+
@@ -167,4 +167,4 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
167167
+------------------------------------+---------------------------+-----------------------------------+
168168

169169
.. [#] ``std::filesystem::path`` is converted to ``pathlib.Path`` and
170-
``os.PathLike`` is converted to ``std::filesystem::path``.
170+
can be loaded from ``os.PathLike``, ``str``, and ``bytes``.

docs/benchmark.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def generate_dummy_code_boost(nclasses=10):
4848
decl += "\n"
4949

5050
for cl in range(nclasses):
51-
decl += "class cl%03i {\n" % cl
51+
decl += f"class cl{cl:03} {{\n"
5252
decl += "public:\n"
5353
bindings += f' py::class_<cl{cl:03}>("cl{cl:03}")\n'
5454
for fn in range(nfns):
@@ -85,5 +85,5 @@ def generate_dummy_code_boost(nclasses=10):
8585
n2 = dt.datetime.now()
8686
elapsed = (n2 - n1).total_seconds()
8787
size = os.stat("test.so").st_size
88-
print(" {%i, %f, %i}," % (nclasses * nfns, elapsed, size))
88+
print(f" {{{nclasses * nfns}, {elapsed:.6f}, {size}}},")
8989
print("}")

include/pybind11/cast.h

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,39 @@ PYBIND11_WARNING_DISABLE_MSVC(4127)
3434

3535
PYBIND11_NAMESPACE_BEGIN(detail)
3636

37+
// Type trait checker for `descr`
38+
template <typename>
39+
struct is_descr : std::false_type {};
40+
41+
template <size_t N, typename... Ts>
42+
struct is_descr<descr<N, Ts...>> : std::true_type {};
43+
44+
template <size_t N, typename... Ts>
45+
struct is_descr<const descr<N, Ts...>> : std::true_type {};
46+
47+
// Use arg_name instead of name when available
48+
template <typename T, typename SFINAE = void>
49+
struct as_arg_type {
50+
static constexpr auto name = T::name;
51+
};
52+
53+
template <typename T>
54+
struct as_arg_type<T, typename std::enable_if<is_descr<decltype(T::arg_name)>::value>::type> {
55+
static constexpr auto name = T::arg_name;
56+
};
57+
58+
// Use return_name instead of name when available
59+
template <typename T, typename SFINAE = void>
60+
struct as_return_type {
61+
static constexpr auto name = T::name;
62+
};
63+
64+
template <typename T>
65+
struct as_return_type<T,
66+
typename std::enable_if<is_descr<decltype(T::return_name)>::value>::type> {
67+
static constexpr auto name = T::return_name;
68+
};
69+
3770
template <typename type, typename SFINAE = void>
3871
class type_caster : public type_caster_base<type> {};
3972
template <typename type>
@@ -1140,18 +1173,20 @@ using type_caster_holder = conditional_t<is_copy_constructible<holder_type>::val
11401173
copyable_holder_caster<type, holder_type>,
11411174
move_only_holder_caster<type, holder_type>>;
11421175

1143-
template <typename T, bool Value = false>
1144-
struct always_construct_holder {
1176+
template <bool Value = false>
1177+
struct always_construct_holder_value {
11451178
static constexpr bool value = Value;
11461179
};
11471180

1181+
template <typename T, bool Value = false>
1182+
struct always_construct_holder : always_construct_holder_value<Value> {};
1183+
11481184
/// Create a specialization for custom holder types (silently ignores std::shared_ptr)
11491185
#define PYBIND11_DECLARE_HOLDER_TYPE(type, holder_type, ...) \
11501186
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) \
11511187
namespace detail { \
11521188
template <typename type> \
1153-
struct always_construct_holder<holder_type> : always_construct_holder<void, ##__VA_ARGS__> { \
1154-
}; \
1189+
struct always_construct_holder<holder_type> : always_construct_holder_value<__VA_ARGS__> {}; \
11551190
template <typename type> \
11561191
class type_caster<holder_type, enable_if_t<!is_shared_ptr<holder_type>::value>> \
11571192
: public type_caster_holder<type, holder_type> {}; \
@@ -1361,6 +1396,8 @@ struct pyobject_caster {
13611396
return src.inc_ref();
13621397
}
13631398
PYBIND11_TYPE_CASTER(type, handle_type_name<type>::name);
1399+
static constexpr auto arg_name = as_arg_type<handle_type_name<type>>::name;
1400+
static constexpr auto return_name = as_return_type<handle_type_name<type>>::name;
13641401
};
13651402

13661403
template <typename T>
@@ -1889,7 +1926,7 @@ class argument_loader {
18891926
"py::args cannot be specified more than once");
18901927

18911928
static constexpr auto arg_names
1892-
= ::pybind11::detail::concat(type_descr(make_caster<Args>::name)...);
1929+
= ::pybind11::detail::concat(type_descr(as_arg_type<make_caster<Args>>::name)...);
18931930

18941931
bool load_args(function_call &call) { return load_impl_sequence(call, indices{}); }
18951932

0 commit comments

Comments
 (0)