mirror of
https://github.com/pybind/pybind11.git
synced 2026-05-13 17:56:02 +00:00
Support keyword-only arguments
This adds support for a `py::args_kw_only()` annotation that can be
specified between `py::arg` annotations to indicate that any following
arguments are keyword-only. This allows you to write:
m.def("f", [](int a, int b) { /* ... */ },
py::arg("a"), py::args_kw_only(), py::arg("b"));
and have it work like Python 3's:
def f(a, *, b):
# ...
with respect to how `a` and `b` arguments are accepted (that is, `a` can
be positional or by keyword; `b` can only be specified by keyword).
This commit is contained in:
committed by
Wenzel Jakob
parent
03f9e4a8ec
commit
be0d804523
@@ -137,7 +137,8 @@ struct argument_record {
|
||||
struct function_record {
|
||||
function_record()
|
||||
: is_constructor(false), is_new_style_constructor(false), is_stateless(false),
|
||||
is_operator(false), has_args(false), has_kwargs(false), is_method(false) { }
|
||||
is_operator(false), is_method(false),
|
||||
has_args(false), has_kwargs(false), has_kw_only_args(false) { }
|
||||
|
||||
/// Function name
|
||||
char *name = nullptr; /* why no C++ strings? They generate heavier code.. */
|
||||
@@ -175,18 +176,24 @@ struct function_record {
|
||||
/// True if this is an operator (__add__), etc.
|
||||
bool is_operator : 1;
|
||||
|
||||
/// True if this is a method
|
||||
bool is_method : 1;
|
||||
|
||||
/// True if the function has a '*args' argument
|
||||
bool has_args : 1;
|
||||
|
||||
/// True if the function has a '**kwargs' argument
|
||||
bool has_kwargs : 1;
|
||||
|
||||
/// True if this is a method
|
||||
bool is_method : 1;
|
||||
/// True once a 'py::args_kw_only' is encountered (any following args are keyword-only)
|
||||
bool has_kw_only_args : 1;
|
||||
|
||||
/// Number of arguments (including py::args and/or py::kwargs, if present)
|
||||
std::uint16_t nargs;
|
||||
|
||||
/// Number of trailing arguments (counted in `nargs`) that are keyword-only
|
||||
std::uint16_t nargs_kwonly = 0;
|
||||
|
||||
/// Python method object
|
||||
PyMethodDef *def = nullptr;
|
||||
|
||||
@@ -359,12 +366,20 @@ template <> struct process_attribute<is_new_style_constructor> : process_attribu
|
||||
static void init(const is_new_style_constructor &, function_record *r) { r->is_new_style_constructor = true; }
|
||||
};
|
||||
|
||||
inline void process_kwonly_arg(const arg &a, function_record *r) {
|
||||
if (!a.name || strlen(a.name) == 0)
|
||||
pybind11_fail("arg(): cannot specify an unnamed argument after an args_kw_only() annotation");
|
||||
++r->nargs_kwonly;
|
||||
}
|
||||
|
||||
/// Process a keyword argument attribute (*without* a default value)
|
||||
template <> struct process_attribute<arg> : process_attribute_default<arg> {
|
||||
static void init(const arg &a, function_record *r) {
|
||||
if (r->is_method && r->args.empty())
|
||||
r->args.emplace_back("self", nullptr, handle(), true /*convert*/, false /*none not allowed*/);
|
||||
r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none);
|
||||
|
||||
if (r->has_kw_only_args) process_kwonly_arg(a, r);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -396,6 +411,15 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
|
||||
#endif
|
||||
}
|
||||
r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none);
|
||||
|
||||
if (r->has_kw_only_args) process_kwonly_arg(a, r);
|
||||
}
|
||||
};
|
||||
|
||||
/// Process a keyword-only-arguments-follow pseudo argument
|
||||
template <> struct process_attribute<args_kw_only> : process_attribute_default<args_kw_only> {
|
||||
static void init(const args_kw_only &, function_record *r) {
|
||||
r->has_kw_only_args = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1887,6 +1887,11 @@ public:
|
||||
#endif
|
||||
};
|
||||
|
||||
/// \ingroup annotations
|
||||
/// Annotation indicating that all following arguments are keyword-only; the is the equivalent of an
|
||||
/// unnamed '*' argument (in Python 3)
|
||||
struct args_kw_only {};
|
||||
|
||||
template <typename T>
|
||||
arg_v arg::operator=(T &&value) const { return {std::move(*this), std::forward<T>(value)}; }
|
||||
|
||||
|
||||
@@ -168,6 +168,14 @@ protected:
|
||||
/* Process any user-provided function attributes */
|
||||
process_attributes<Extra...>::init(extra..., rec);
|
||||
|
||||
{
|
||||
constexpr bool has_kw_only_args = any_of<std::is_same<args_kw_only, Extra>...>::value,
|
||||
has_args = any_of<std::is_same<args, Args>...>::value,
|
||||
has_arg_annotations = any_of<is_keyword<Extra>...>::value;
|
||||
static_assert(has_arg_annotations || !has_kw_only_args, "py::args_kw_only requires the use of argument annotations");
|
||||
static_assert(!(has_args && has_kw_only_args), "py::args_kw_only cannot be combined with a py::args argument");
|
||||
}
|
||||
|
||||
/* Generate a readable signature describing the function's arguments and return value types */
|
||||
static constexpr auto signature = _("(") + cast_in::arg_names + _(") -> ") + cast_out::name;
|
||||
PYBIND11_DESCR_CONSTEXPR auto types = decltype(signature)::types();
|
||||
@@ -483,15 +491,16 @@ protected:
|
||||
*/
|
||||
|
||||
const function_record &func = *it;
|
||||
size_t pos_args = func.nargs; // Number of positional arguments that we need
|
||||
if (func.has_args) --pos_args; // (but don't count py::args
|
||||
if (func.has_kwargs) --pos_args; // or py::kwargs)
|
||||
size_t num_args = func.nargs; // Number of positional arguments that we need
|
||||
if (func.has_args) --num_args; // (but don't count py::args
|
||||
if (func.has_kwargs) --num_args; // or py::kwargs)
|
||||
size_t pos_args = num_args - func.nargs_kwonly;
|
||||
|
||||
if (!func.has_args && n_args_in > pos_args)
|
||||
continue; // Too many arguments for this overload
|
||||
continue; // Too many positional arguments for this overload
|
||||
|
||||
if (n_args_in < pos_args && func.args.size() < pos_args)
|
||||
continue; // Not enough arguments given, and not enough defaults to fill in the blanks
|
||||
continue; // Not enough positional arguments given, and not enough defaults to fill in the blanks
|
||||
|
||||
function_call call(func, parent);
|
||||
|
||||
@@ -535,10 +544,10 @@ protected:
|
||||
dict kwargs = reinterpret_borrow<dict>(kwargs_in);
|
||||
|
||||
// 2. Check kwargs and, failing that, defaults that may help complete the list
|
||||
if (args_copied < pos_args) {
|
||||
if (args_copied < num_args) {
|
||||
bool copied_kwargs = false;
|
||||
|
||||
for (; args_copied < pos_args; ++args_copied) {
|
||||
for (; args_copied < num_args; ++args_copied) {
|
||||
const auto &arg = func.args[args_copied];
|
||||
|
||||
handle value;
|
||||
@@ -564,7 +573,7 @@ protected:
|
||||
break;
|
||||
}
|
||||
|
||||
if (args_copied < pos_args)
|
||||
if (args_copied < num_args)
|
||||
continue; // Not enough arguments, defaults, or kwargs to fill the positional arguments
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user