Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow subclasses of py::args and py::kwargs #5381

Merged
merged 11 commits into from
Sep 24, 2024
4 changes: 2 additions & 2 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -1570,9 +1570,9 @@ class argument_loader {
using indices = make_index_sequence<sizeof...(Args)>;

template <typename Arg>
using argument_is_args = std::is_same<intrinsic_t<Arg>, args>;
using argument_is_args = std::is_base_of<args, intrinsic_t<Arg>>;
template <typename Arg>
using argument_is_kwargs = std::is_same<intrinsic_t<Arg>, kwargs>;
using argument_is_kwargs = std::is_base_of<kwargs, intrinsic_t<Arg>>;
// Get kwargs argument position, or -1 if not present:
static constexpr auto kwargs_pos = constexpr_last<argument_is_kwargs, Args...>();

Expand Down
26 changes: 26 additions & 0 deletions tests/test_kwargs_and_defaults.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@

#include <utility>

// Classes needed for subclass test.
class ArgsSubclass : public py::args {
using py::args::args;
};
class KWArgsSubclass : public py::kwargs {
using py::kwargs::kwargs;
};
namespace pybind11 {
namespace detail {
template <>
struct handle_type_name<ArgsSubclass> {
static constexpr auto name = const_name("*Args");
};
template <>
struct handle_type_name<KWArgsSubclass> {
static constexpr auto name = const_name("**KWArgs");
};
} // namespace detail
} // namespace pybind11

TEST_SUBMODULE(kwargs_and_defaults, m) {
auto kw_func
= [](int x, int y) { return "x=" + std::to_string(x) + ", y=" + std::to_string(y); };
Expand Down Expand Up @@ -322,4 +342,10 @@ TEST_SUBMODULE(kwargs_and_defaults, m) {
py::pos_only{},
py::arg("i"),
py::arg("j"));

// Test support for args and kwargs subclasses
m.def("args_kwargs_subclass_function",
[](const ArgsSubclass &args, const KWArgsSubclass &kwargs) {
return py::make_tuple(args, kwargs);
});
}
11 changes: 11 additions & 0 deletions tests/test_kwargs_and_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ def test_function_signatures(doc):
assert (
doc(m.args_kwargs_function) == "args_kwargs_function(*args, **kwargs) -> tuple"
)
assert (
doc(m.args_kwargs_subclass_function)
== "args_kwargs_subclass_function(*Args, **KWArgs) -> tuple"
)
assert (
doc(m.KWClass.foo0)
== "foo0(self: m.kwargs_and_defaults.KWClass, arg0: int, arg1: float) -> None"
Expand Down Expand Up @@ -98,6 +102,7 @@ def test_arg_and_kwargs():
args = "a1", "a2"
kwargs = {"arg3": "a3", "arg4": 4}
assert m.args_kwargs_function(*args, **kwargs) == (args, kwargs)
assert m.args_kwargs_subclass_function(*args, **kwargs) == (args, kwargs)


def test_mixed_args_and_kwargs(msg):
Expand Down Expand Up @@ -413,6 +418,12 @@ def test_args_refcount():
)
assert refcount(myval) == expected

assert m.args_kwargs_subclass_function(7, 8, myval, a=1, b=myval) == (
(7, 8, myval),
{"a": 1, "b": myval},
)
assert refcount(myval) == expected

exp3 = refcount(myval, myval, myval)
assert m.args_refcount(myval, myval, myval) == (exp3, exp3, exp3)
assert refcount(myval) == expected
Expand Down