-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Add support for positional args with args/kwargs #611
Conversation
9a4c6a4
to
80b04cb
Compare
Pushed a second commit to avoid needing to create a tuple in favour of a lighter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. Having the dispatcher return all the args in a single container makes a lot of sense. +1 for std::vector
.
: assert_args_kwargs_must_be_last<false, true, sizeof...(T) == 0> {}; | ||
template <typename... T> struct args_kwargs_must_be_last<args, kwargs, T...> | ||
: assert_args_kwargs_must_be_last<true, true, sizeof...(T) == 0> {}; | ||
template <> struct args_kwargs_must_be_last<> : assert_args_kwargs_must_be_last<false, false, true> {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of the custom metafunction, couldn't this reuse constexpr_last
and constexpr_first
which are already used for a similar purpose in the unpacking_collector
version of collect_arguments()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I tried to figure out a nicer way to do that, but couldn't think of one. But actually, let me look again, I have an idea...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, I didn't realize we had constexpr_first
. I have to move it anyway (it's defined after the argument loader), so I'm going to move it to common.h (to put it with constexpr_sum
).
static_assert(detail::expected_num_args<Extra...>(sizeof...(Args)), | ||
constexpr bool have_args = detail::any_of<std::is_same<args, detail::intrinsic_t<Args>>...>::value, | ||
have_kwargs = detail::any_of<std::is_same<kwargs, detail::intrinsic_t<Args>>...>::value; | ||
static_assert(detail::expected_num_args<Extra...>(sizeof...(Args), have_args, have_kwargs), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to recompute have_args
and has_kwargs
-- the info is already available as cast_in::has_args
and cast_in::has_kwargs
. The static_assert
just needs to be placed after using cast_in
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, of course it is! Good catch.
} | ||
} | ||
pass_args[args_copied++] = extra_args; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider .reserve()
+ .push_back()
as more idiomatic C++, in place of pass_args[args_copied++]
. Unless this complicates matters elsewhere, then dissregard. (But generally with .reserve()
it should be args_copied == pass_args.size()
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had it that way originally, but then I figured this way might be slightly more optimal as I know the number of elements I need exactly (and so avoid the std::vector
needing to update its internal size state variable n
times). But in retrospect, that's probably microoptimization (and I'm not accounting for the fact that as as I need to invoke n
empty handle
s and then invoke n
handle copy assignments.) So yeah, I'll change it back.
with pytest.raises(TypeError): | ||
assert mpakd(1, i=1) | ||
with pytest.raises(TypeError): | ||
assert mpakd(1, 2, j=1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to also verify that the error messages are correctly formed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@dean0x7d - is there any simple way I can mute that line length error (see the build failure)? This doesn't really seem like a case I can easily shorten. |
ed90543
to
cea4f42
Compare
Yes, with |
This commit rewrites the function dispatcher code to support mixing regular arguments with py::args/py::kwargs arguments. It also simplifies the argument loader noticeably as it no longer has to worry about args/kwargs: all of that is now sorted out in the dispatcher, which now simply appends a tuple/dict if the function takes py::args/py::kwargs, then passes all the arguments in a vector. When the argument loader hit a py::args or py::kwargs, it doesn't do anything special: it just calls the appropriate type_caster just like it does for any other argument (thus removing the previous special cases for args/kwargs). Switching to passing arguments in a single std::vector instead of a pair of tuples also makes things simpler, both in the dispatch and the argument_loader: since this argument list is strictly pybind-internal (i.e. it never goes to Python) we have no particular reason to use a Python tuple here. Some (intentional) restrictions: - you may not bind a function that has args/kwargs somewhere other than the end (this somewhat matches Python, and keeps the dispatch code a little cleaner by being able to not worry about where to inject the args/kwargs in the argument list). - If you specify an argument both positionally and via a keyword argument, you get a TypeError alerting you to this (as you do in Python).
Passing a negative value wasn't valid anyway, and moreover this avoids a little bit of extra code to avoid signed/unsigned argument warnings.
This keeps it with constexpr_sum and the other metafunctions.
e5c168c
to
9755491
Compare
Perfect. I squashed everything, incorporating all your suggestions. |
37cf37b
to
a3ac042
Compare
This cleans up the previous commit slightly by further reducing the function call arguments to a single struct (containing the function_record, arguments vector, and parent). Although this doesn't currently change anything, it does allow for future functionality to have a place for precalls to store temporary objects that need to be destroyed after a function call (whether or not the call succeeds). As a concrete example, with this change pybind#625 could be easily implemented (I think) by adding a std::unique_ptr<gil_scoped_release> member to the `function_call` struct with a precall that actually constructs it. Without this, the precall can't do that: the postcall won't be invoked if the call throws an exception. This doesn't seems to affect the .so size noticeably (either way).
a3ac042
to
59ecf31
Compare
Thank you, this is amazing -- the new dispatch code looks very simple and clean. Merged just now outside of github to fix a conflict manually. |
This commit rewrites the function dispatcher code to support mixing regular arguments with
py::args
/py::kwargs
arguments. It also simplifies the argument loader noticeably as it no longer has to worry about args/kwargs: all of that is now sorted out in the dispatcher, which now simply appends a tuple/dict if the function takespy::args
/py::kwargs
, then passes all the arguments in a single tuple.Some (intentional) restrictions:
py::args
/py::kwarg
s in the argument list. Fully matching what Python allows (i.e. named, but not positional, arguments after*args
) seems not worth bothering with.