mirror of
https://github.com/pybind/pybind11.git
synced 2026-06-08 15:29:45 +00:00
Option for arg/return type hints and correct typing for std::filesystem::path (#5450)
* Added arg/return type handling. * Added support for nested arg/return type in py::typing::List * Added support for arg/return type in stl/filesystem * Added tests for arg/return type in stl/filesystem and py::typing::List * Added arg/return name to more py::typing classes * Added arg/return type to Callable[...] * Added tests for typing container classes (also nested) * Changed typing classes to avoid using C++14 auto return type deduction. * Fixed clang-tidy errors. * Changed Enable to SFINAE * Added test for Tuple[T, ...] * Added RealNumber with custom caster for testing typing classes. * Added tests for Set, Iterable, Iterator, Union, and Optional * Added tests for Callable * Fixed Callable with ellipsis test * Changed TypeGuard/TypeIs to use return type (being the narrower type) + Tests * Added test for use of fallback type name with stl vector * Updated documentation. * Fixed unnecessary constructor call in test. * Fixed reference counting in example type caster. * Fixed clang-tidy issues. * Fix for clang-tidy * Updated cast method to use pybind11 API rather than Python C API in custom caster example * Updated load to use pybind11 API rather than Python C API in custom caster example * Changed test of arg/return name to use pybind11 API instead of Python C API * Updated code in adcanced/cast example and improved documentation text * Fixed references in custom type caster docs * Fixed wrong logical and operator in test * Fixed wrong logical operator in doc example * Added comment to test about `float` vs `float | int` * Updated std::filesystem::path docs in cast/overview section * Remove one stray dot. --------- Co-authored-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include <pybind11/stl.h>
|
||||
#include <pybind11/typing.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
@@ -137,6 +138,44 @@ typedef py::typing::TypeVar<"V"> TypeVarV;
|
||||
} // namespace typevar
|
||||
#endif
|
||||
|
||||
// Custom type for testing arg_name/return_name type hints
|
||||
// RealNumber:
|
||||
// * in arguments -> float | int
|
||||
// * in return -> float
|
||||
// * fallback -> complex
|
||||
// The choice of types is not really useful, but just made different for testing purposes.
|
||||
// According to `PEP 484 – Type Hints` annotating with `float` also allows `int`,
|
||||
// so using `float | int` could be replaced by just `float`.
|
||||
|
||||
struct RealNumber {
|
||||
double value;
|
||||
};
|
||||
|
||||
namespace pybind11 {
|
||||
namespace detail {
|
||||
|
||||
template <>
|
||||
struct type_caster<RealNumber> {
|
||||
PYBIND11_TYPE_CASTER(RealNumber, const_name("complex"));
|
||||
static constexpr auto arg_name = const_name("Union[float, int]");
|
||||
static constexpr auto return_name = const_name("float");
|
||||
|
||||
static handle cast(const RealNumber &number, return_value_policy, handle) {
|
||||
return py::float_(number.value).release();
|
||||
}
|
||||
|
||||
bool load(handle src, bool) {
|
||||
if (!py::isinstance<py::float_>(src) && !py::isinstance<py::int_>(src)) {
|
||||
return false;
|
||||
}
|
||||
value.value = src.cast<double>();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
|
||||
TEST_SUBMODULE(pytypes, m) {
|
||||
m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); });
|
||||
|
||||
@@ -998,4 +1037,94 @@ TEST_SUBMODULE(pytypes, m) {
|
||||
#else
|
||||
m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false;
|
||||
#endif
|
||||
m.def("half_of_number", [](const RealNumber &x) { return RealNumber{x.value / 2}; });
|
||||
// std::vector<T>
|
||||
m.def("half_of_number_vector", [](const std::vector<RealNumber> &x) {
|
||||
std::vector<RealNumber> result;
|
||||
result.reserve(x.size());
|
||||
for (auto num : x) {
|
||||
result.push_back(RealNumber{num.value / 2});
|
||||
}
|
||||
return result;
|
||||
});
|
||||
// Tuple<T, T>
|
||||
m.def("half_of_number_tuple", [](const py::typing::Tuple<RealNumber, RealNumber> &x) {
|
||||
py::typing::Tuple<RealNumber, RealNumber> result
|
||||
= py::make_tuple(RealNumber{x[0].cast<RealNumber>().value / 2},
|
||||
RealNumber{x[1].cast<RealNumber>().value / 2});
|
||||
return result;
|
||||
});
|
||||
// Tuple<T, ...>
|
||||
m.def("half_of_number_tuple_ellipsis",
|
||||
[](const py::typing::Tuple<RealNumber, py::ellipsis> &x) {
|
||||
py::typing::Tuple<RealNumber, py::ellipsis> result(x.size());
|
||||
for (size_t i = 0; i < x.size(); ++i) {
|
||||
result[i] = x[i].cast<RealNumber>().value / 2;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
// Dict<K, V>
|
||||
m.def("half_of_number_dict", [](const py::typing::Dict<std::string, RealNumber> &x) {
|
||||
py::typing::Dict<std::string, RealNumber> result;
|
||||
for (auto it : x) {
|
||||
result[it.first] = RealNumber{it.second.cast<RealNumber>().value / 2};
|
||||
}
|
||||
return result;
|
||||
});
|
||||
// List<T>
|
||||
m.def("half_of_number_list", [](const py::typing::List<RealNumber> &x) {
|
||||
py::typing::List<RealNumber> result;
|
||||
for (auto num : x) {
|
||||
result.append(RealNumber{num.cast<RealNumber>().value / 2});
|
||||
}
|
||||
return result;
|
||||
});
|
||||
// List<List<T>>
|
||||
m.def("half_of_number_nested_list",
|
||||
[](const py::typing::List<py::typing::List<RealNumber>> &x) {
|
||||
py::typing::List<py::typing::List<RealNumber>> result_lists;
|
||||
for (auto nums : x) {
|
||||
py::typing::List<RealNumber> result;
|
||||
for (auto num : nums) {
|
||||
result.append(RealNumber{num.cast<RealNumber>().value / 2});
|
||||
}
|
||||
result_lists.append(result);
|
||||
}
|
||||
return result_lists;
|
||||
});
|
||||
// Set<T>
|
||||
m.def("identity_set", [](const py::typing::Set<RealNumber> &x) { return x; });
|
||||
// Iterable<T>
|
||||
m.def("identity_iterable", [](const py::typing::Iterable<RealNumber> &x) { return x; });
|
||||
// Iterator<T>
|
||||
m.def("identity_iterator", [](const py::typing::Iterator<RealNumber> &x) { return x; });
|
||||
// Callable<R(A)>
|
||||
m.def("apply_callable",
|
||||
[](const RealNumber &x, const py::typing::Callable<RealNumber(const RealNumber &)> &f) {
|
||||
return f(x).cast<RealNumber>();
|
||||
});
|
||||
// Callable<R(...)>
|
||||
m.def("apply_callable_ellipsis",
|
||||
[](const RealNumber &x, const py::typing::Callable<RealNumber(py::ellipsis)> &f) {
|
||||
return f(x).cast<RealNumber>();
|
||||
});
|
||||
// Union<T1, T2>
|
||||
m.def("identity_union", [](const py::typing::Union<RealNumber, std::string> &x) { return x; });
|
||||
// Optional<T>
|
||||
m.def("identity_optional", [](const py::typing::Optional<RealNumber> &x) { return x; });
|
||||
// TypeGuard<T>
|
||||
m.def("check_type_guard",
|
||||
[](const py::typing::List<py::object> &x)
|
||||
-> py::typing::TypeGuard<py::typing::List<RealNumber>> {
|
||||
for (const auto &item : x) {
|
||||
if (!py::isinstance<RealNumber>(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// TypeIs<T>
|
||||
m.def("check_type_is", [](const py::object &x) -> py::typing::TypeIs<RealNumber> {
|
||||
return py::isinstance<RealNumber>(x);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user