mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 06:35:12 +00:00
Add support for positional args with args/kwargs
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).
This commit is contained in:
parent
102c94fc38
commit
2686da8350
@ -256,16 +256,21 @@ Such functions can also be created using pybind11:
|
|||||||
m.def("generic", &generic);
|
m.def("generic", &generic);
|
||||||
|
|
||||||
The class ``py::args`` derives from ``py::tuple`` and ``py::kwargs`` derives
|
The class ``py::args`` derives from ``py::tuple`` and ``py::kwargs`` derives
|
||||||
from ``py::dict``. Note that the ``kwargs`` argument is invalid if no keyword
|
from ``py::dict``.
|
||||||
arguments were actually provided. Please refer to the other examples for
|
|
||||||
details on how to iterate over these, and on how to cast their entries into
|
|
||||||
C++ objects. A demonstration is also available in
|
|
||||||
``tests/test_kwargs_and_defaults.cpp``.
|
|
||||||
|
|
||||||
.. warning::
|
You may also use just one or the other, and may combine these with other
|
||||||
|
arguments as long as the ``py::args`` and ``py::kwargs`` arguments are the last
|
||||||
|
arguments accepted by the function.
|
||||||
|
|
||||||
Unlike Python, pybind11 does not allow combining normal parameters with the
|
Please refer to the other examples for details on how to iterate over these,
|
||||||
``args`` / ``kwargs`` special parameters.
|
and on how to cast their entries into C++ objects. A demonstration is also
|
||||||
|
available in ``tests/test_kwargs_and_defaults.cpp``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When combining \*args or \*\*kwargs with :ref:`keyword_args` you should
|
||||||
|
*not* include ``py::arg`` tags for the ``py::args`` and ``py::kwargs``
|
||||||
|
arguments.
|
||||||
|
|
||||||
Default arguments revisited
|
Default arguments revisited
|
||||||
===========================
|
===========================
|
||||||
|
@ -69,7 +69,7 @@ struct undefined_t;
|
|||||||
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_;
|
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_;
|
||||||
template <typename... Args> struct init;
|
template <typename... Args> struct init;
|
||||||
template <typename... Args> struct init_alias;
|
template <typename... Args> struct init_alias;
|
||||||
inline void keep_alive_impl(int Nurse, int Patient, handle args, handle ret);
|
inline void keep_alive_impl(int Nurse, int Patient, function_arguments args, handle ret);
|
||||||
|
|
||||||
/// Internal data structure which holds metadata about a keyword argument
|
/// Internal data structure which holds metadata about a keyword argument
|
||||||
struct argument_record {
|
struct argument_record {
|
||||||
@ -100,7 +100,7 @@ struct function_record {
|
|||||||
std::vector<argument_record> args;
|
std::vector<argument_record> args;
|
||||||
|
|
||||||
/// Pointer to lambda function which converts arguments and performs the actual call
|
/// Pointer to lambda function which converts arguments and performs the actual call
|
||||||
handle (*impl) (function_record *, handle, handle, handle) = nullptr;
|
handle (*impl) (function_record *, function_arguments, handle) = nullptr;
|
||||||
|
|
||||||
/// Storage for the wrapped function pointer and captured data, if any
|
/// Storage for the wrapped function pointer and captured data, if any
|
||||||
void *data[3] = { };
|
void *data[3] = { };
|
||||||
@ -129,7 +129,7 @@ struct function_record {
|
|||||||
/// True if this is a method
|
/// True if this is a method
|
||||||
bool is_method : 1;
|
bool is_method : 1;
|
||||||
|
|
||||||
/// Number of arguments
|
/// Number of arguments (including py::args and/or py::kwargs, if present)
|
||||||
uint16_t nargs;
|
uint16_t nargs;
|
||||||
|
|
||||||
/// Python method object
|
/// Python method object
|
||||||
@ -233,8 +233,8 @@ template <typename T> struct process_attribute_default {
|
|||||||
/// Default implementation: do nothing
|
/// Default implementation: do nothing
|
||||||
static void init(const T &, function_record *) { }
|
static void init(const T &, function_record *) { }
|
||||||
static void init(const T &, type_record *) { }
|
static void init(const T &, type_record *) { }
|
||||||
static void precall(handle) { }
|
static void precall(function_arguments) { }
|
||||||
static void postcall(handle, handle) { }
|
static void postcall(function_arguments, handle) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Process an attribute specifying the function's name
|
/// Process an attribute specifying the function's name
|
||||||
@ -362,13 +362,13 @@ struct process_attribute<arithmetic> : process_attribute_default<arithmetic> {};
|
|||||||
*/
|
*/
|
||||||
template <int Nurse, int Patient> struct process_attribute<keep_alive<Nurse, Patient>> : public process_attribute_default<keep_alive<Nurse, Patient>> {
|
template <int Nurse, int Patient> struct process_attribute<keep_alive<Nurse, Patient>> : public process_attribute_default<keep_alive<Nurse, Patient>> {
|
||||||
template <int N = Nurse, int P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
|
template <int N = Nurse, int P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
|
||||||
static void precall(handle args) { keep_alive_impl(Nurse, Patient, args, handle()); }
|
static void precall(function_arguments args) { keep_alive_impl(Nurse, Patient, args, handle()); }
|
||||||
template <int N = Nurse, int P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
|
template <int N = Nurse, int P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
|
||||||
static void postcall(handle, handle) { }
|
static void postcall(function_arguments, handle) { }
|
||||||
template <int N = Nurse, int P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
|
template <int N = Nurse, int P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
|
||||||
static void precall(handle) { }
|
static void precall(function_arguments) { }
|
||||||
template <int N = Nurse, int P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
|
template <int N = Nurse, int P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
|
||||||
static void postcall(handle args, handle ret) { keep_alive_impl(Nurse, Patient, args, ret); }
|
static void postcall(function_arguments args, handle ret) { keep_alive_impl(Nurse, Patient, args, ret); }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Recursively iterate over variadic template arguments
|
/// Recursively iterate over variadic template arguments
|
||||||
@ -381,11 +381,11 @@ template <typename... Args> struct process_attributes {
|
|||||||
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::init(args, r), 0) ... };
|
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::init(args, r), 0) ... };
|
||||||
ignore_unused(unused);
|
ignore_unused(unused);
|
||||||
}
|
}
|
||||||
static void precall(handle fn_args) {
|
static void precall(function_arguments fn_args) {
|
||||||
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::precall(fn_args), 0) ... };
|
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::precall(fn_args), 0) ... };
|
||||||
ignore_unused(unused);
|
ignore_unused(unused);
|
||||||
}
|
}
|
||||||
static void postcall(handle fn_args, handle fn_ret) {
|
static void postcall(function_arguments fn_args, handle fn_ret) {
|
||||||
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::postcall(fn_args, fn_ret), 0) ... };
|
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::postcall(fn_args, fn_ret), 0) ... };
|
||||||
ignore_unused(unused);
|
ignore_unused(unused);
|
||||||
}
|
}
|
||||||
@ -395,8 +395,8 @@ template <typename... Args> struct process_attributes {
|
|||||||
template <typename... Extra,
|
template <typename... Extra,
|
||||||
size_t named = constexpr_sum(std::is_base_of<arg, Extra>::value...),
|
size_t named = constexpr_sum(std::is_base_of<arg, Extra>::value...),
|
||||||
size_t self = constexpr_sum(std::is_same<is_method, Extra>::value...)>
|
size_t self = constexpr_sum(std::is_same<is_method, Extra>::value...)>
|
||||||
constexpr bool expected_num_args(size_t nargs) {
|
constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) {
|
||||||
return named == 0 || (self + named) == nargs;
|
return named == 0 || (self + named + has_args + has_kwargs) == nargs;
|
||||||
}
|
}
|
||||||
|
|
||||||
NAMESPACE_END(detail)
|
NAMESPACE_END(detail)
|
||||||
|
@ -1245,22 +1245,42 @@ constexpr arg operator"" _a(const char *name, size_t) { return arg(name); }
|
|||||||
|
|
||||||
NAMESPACE_BEGIN(detail)
|
NAMESPACE_BEGIN(detail)
|
||||||
|
|
||||||
|
// forward declaration
|
||||||
|
struct function_record;
|
||||||
|
|
||||||
|
// Helper struct to only allow py::args and/or py::kwargs at the end of the function arguments
|
||||||
|
template <bool args, bool kwargs, bool args_kwargs_are_last> struct assert_args_kwargs_must_be_last {
|
||||||
|
static constexpr bool has_args = args, has_kwargs = kwargs;
|
||||||
|
static_assert(args_kwargs_are_last, "py::args/py::kwargs are only permitted as the last argument(s) of a function");
|
||||||
|
};
|
||||||
|
template <typename... T> struct args_kwargs_must_be_last;
|
||||||
|
template <typename T1, typename... Tmore> struct args_kwargs_must_be_last<T1, Tmore...>
|
||||||
|
: args_kwargs_must_be_last<Tmore...> {};
|
||||||
|
template <typename... T> struct args_kwargs_must_be_last<args, T...>
|
||||||
|
: assert_args_kwargs_must_be_last<true, false, sizeof...(T) == 0> {};
|
||||||
|
template <typename... T> struct args_kwargs_must_be_last<kwargs, T...>
|
||||||
|
: 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> {};
|
||||||
|
|
||||||
|
using function_arguments = const std::vector<handle> &;
|
||||||
|
|
||||||
/// Helper class which loads arguments for C++ functions called from Python
|
/// Helper class which loads arguments for C++ functions called from Python
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
class argument_loader {
|
class argument_loader {
|
||||||
using itypes = type_list<intrinsic_t<Args>...>;
|
|
||||||
using indices = make_index_sequence<sizeof...(Args)>;
|
using indices = make_index_sequence<sizeof...(Args)>;
|
||||||
|
|
||||||
public:
|
using check_args_kwargs = args_kwargs_must_be_last<intrinsic_t<Args>...>;
|
||||||
argument_loader() : value() {} // Helps gcc-7 properly initialize value
|
|
||||||
|
|
||||||
static constexpr auto has_kwargs = std::is_same<itypes, type_list<args, kwargs>>::value;
|
public:
|
||||||
static constexpr auto has_args = has_kwargs || std::is_same<itypes, type_list<args>>::value;
|
static constexpr bool has_kwargs = check_args_kwargs::has_kwargs;
|
||||||
|
static constexpr bool has_args = check_args_kwargs::has_args;
|
||||||
|
|
||||||
static PYBIND11_DESCR arg_names() { return detail::concat(make_caster<Args>::name()...); }
|
static PYBIND11_DESCR arg_names() { return detail::concat(make_caster<Args>::name()...); }
|
||||||
|
|
||||||
bool load_args(handle args, handle kwargs) {
|
bool load_args(function_arguments args) {
|
||||||
return load_impl(args, kwargs, itypes{});
|
return load_impl_sequence(args, indices{});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Return, typename Func>
|
template <typename Return, typename Func>
|
||||||
@ -1275,26 +1295,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool load_impl(handle args_, handle, type_list<args>) {
|
|
||||||
std::get<0>(value).load(args_, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool load_impl(handle args_, handle kwargs_, type_list<args, kwargs>) {
|
static bool load_impl_sequence(function_arguments, index_sequence<>) { return true; }
|
||||||
std::get<0>(value).load(args_, true);
|
|
||||||
std::get<1>(value).load(kwargs_, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool load_impl(handle args, handle, ... /* anything else */) {
|
|
||||||
return load_impl_sequence(args, indices{});
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool load_impl_sequence(handle, index_sequence<>) { return true; }
|
|
||||||
|
|
||||||
template <size_t... Is>
|
template <size_t... Is>
|
||||||
bool load_impl_sequence(handle src, index_sequence<Is...>) {
|
bool load_impl_sequence(function_arguments args, index_sequence<Is...>) {
|
||||||
for (bool r : {std::get<Is>(value).load(PyTuple_GET_ITEM(src.ptr(), Is), true)...})
|
for (bool r : {std::get<Is>(value).load(args[Is], true)...})
|
||||||
if (!r)
|
if (!r)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
|
@ -82,8 +82,6 @@ protected:
|
|||||||
/// Special internal constructor for functors, lambda functions, etc.
|
/// Special internal constructor for functors, lambda functions, etc.
|
||||||
template <typename Func, typename Return, typename... Args, typename... Extra /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
|
template <typename Func, typename Return, typename... Args, typename... Extra /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
|
||||||
void initialize(Func &&f, Return (*)(Args...) PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) {
|
void initialize(Func &&f, Return (*)(Args...) PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) {
|
||||||
static_assert(detail::expected_num_args<Extra...>(sizeof...(Args)),
|
|
||||||
"The number of named arguments does not match the function signature");
|
|
||||||
|
|
||||||
struct capture { typename std::remove_reference<Func>::type f; };
|
struct capture { typename std::remove_reference<Func>::type f; };
|
||||||
|
|
||||||
@ -116,12 +114,15 @@ protected:
|
|||||||
detail::conditional_t<std::is_void<Return>::value, detail::void_type, Return>
|
detail::conditional_t<std::is_void<Return>::value, detail::void_type, Return>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
static_assert(detail::expected_num_args<Extra...>(sizeof...(Args), cast_in::has_args, cast_in::has_kwargs),
|
||||||
|
"The number of named arguments does not match the function signature");
|
||||||
|
|
||||||
/* Dispatch code which converts function arguments and performs the actual function call */
|
/* Dispatch code which converts function arguments and performs the actual function call */
|
||||||
rec->impl = [](detail::function_record *rec, handle args, handle kwargs, handle parent) -> handle {
|
rec->impl = [](detail::function_record *rec, detail::function_arguments args, handle parent) -> handle {
|
||||||
cast_in args_converter;
|
cast_in args_converter;
|
||||||
|
|
||||||
/* Try to cast the function arguments into the C++ domain */
|
/* Try to cast the function arguments into the C++ domain */
|
||||||
if (!args_converter.load_args(args, kwargs))
|
if (!args_converter.load_args(args))
|
||||||
return PYBIND11_TRY_NEXT_OVERLOAD;
|
return PYBIND11_TRY_NEXT_OVERLOAD;
|
||||||
|
|
||||||
/* Invoke call policy pre-call hook */
|
/* Invoke call policy pre-call hook */
|
||||||
@ -379,66 +380,144 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Main dispatch logic for calls to functions bound using pybind11
|
/// Main dispatch logic for calls to functions bound using pybind11
|
||||||
static PyObject *dispatcher(PyObject *self, PyObject *args, PyObject *kwargs) {
|
static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) {
|
||||||
/* Iterator over the list of potentially admissible overloads */
|
/* Iterator over the list of potentially admissible overloads */
|
||||||
detail::function_record *overloads = (detail::function_record *) PyCapsule_GetPointer(self, nullptr),
|
detail::function_record *overloads = (detail::function_record *) PyCapsule_GetPointer(self, nullptr),
|
||||||
*it = overloads;
|
*it = overloads;
|
||||||
|
|
||||||
/* Need to know how many arguments + keyword arguments there are to pick the right overload */
|
/* Need to know how many arguments + keyword arguments there are to pick the right overload */
|
||||||
size_t nargs = (size_t) PyTuple_GET_SIZE(args),
|
const size_t n_args_in = (size_t) PyTuple_GET_SIZE(args_in);
|
||||||
nkwargs = kwargs ? (size_t) PyDict_Size(kwargs) : 0;
|
|
||||||
|
|
||||||
handle parent = nargs > 0 ? PyTuple_GET_ITEM(args, 0) : nullptr,
|
handle parent = n_args_in > 0 ? PyTuple_GET_ITEM(args_in, 0) : nullptr,
|
||||||
result = PYBIND11_TRY_NEXT_OVERLOAD;
|
result = PYBIND11_TRY_NEXT_OVERLOAD;
|
||||||
try {
|
try {
|
||||||
for (; it != nullptr; it = it->next) {
|
for (; it != nullptr; it = it->next) {
|
||||||
auto args_ = reinterpret_borrow<tuple>(args);
|
|
||||||
size_t kwargs_consumed = 0;
|
|
||||||
|
|
||||||
/* For each overload:
|
/* For each overload:
|
||||||
1. If the required list of arguments is longer than the
|
1. Copy all positional arguments we were given, also checking to make sure that
|
||||||
actually provided amount, create a copy of the argument
|
named positional arguments weren't *also* specified via kwarg.
|
||||||
list and fill in any available keyword/default arguments.
|
2. If we weren't given enough, try to make up the ommitted ones by checking
|
||||||
2. Ensure that all keyword arguments were "consumed"
|
whether they were provided by a kwarg matching the `py::arg("name")` name. If
|
||||||
3. Call the function call dispatcher (function_record::impl)
|
so, use it (and remove it from kwargs; if not, see if the function binding
|
||||||
|
provided a default that we can use.
|
||||||
|
3. Ensure that either all keyword arguments were "consumed", or that the function
|
||||||
|
takes a kwargs argument to accept unconsumed kwargs.
|
||||||
|
4. Any positional arguments still left get put into a tuple (for args), and any
|
||||||
|
leftover kwargs get put into a dict.
|
||||||
|
5. Pack everything into a vector; if we have py::args or py::kwargs, they are an
|
||||||
|
extra tuple or dict at the end of the positional arguments.
|
||||||
|
6. Call the function call dispatcher (function_record::impl)
|
||||||
|
|
||||||
|
If one of these fail, move on to the next overload and keep trying until we get a
|
||||||
|
result other than PYBIND11_TRY_NEXT_OVERLOAD.
|
||||||
*/
|
*/
|
||||||
size_t nargs_ = nargs;
|
|
||||||
if (nargs < it->args.size()) {
|
size_t pos_args = it->nargs; // Number of positional arguments that we need
|
||||||
nargs_ = it->args.size();
|
if (it->has_args) --pos_args; // (but don't count py::args
|
||||||
args_ = tuple(nargs_);
|
if (it->has_kwargs) --pos_args; // or py::kwargs)
|
||||||
for (size_t i = 0; i < nargs; ++i) {
|
|
||||||
handle item = PyTuple_GET_ITEM(args, i);
|
if (!it->has_args && n_args_in > pos_args)
|
||||||
PyTuple_SET_ITEM(args_.ptr(), i, item.inc_ref().ptr());
|
continue; // Too many arguments for this overload
|
||||||
|
|
||||||
|
if (n_args_in < pos_args && it->args.size() < pos_args)
|
||||||
|
continue; // Not enough arguments given, and not enough defaults to fill in the blanks
|
||||||
|
|
||||||
|
std::vector<handle> pass_args;
|
||||||
|
pass_args.reserve(it->nargs);
|
||||||
|
|
||||||
|
size_t args_to_copy = std::min(pos_args, n_args_in);
|
||||||
|
size_t args_copied = 0;
|
||||||
|
|
||||||
|
// 1. Copy any position arguments given.
|
||||||
|
for (; args_copied < args_to_copy; ++args_copied) {
|
||||||
|
// If we find a given positional argument that also has a named kwargs argument,
|
||||||
|
// raise a TypeError like Python does. (We could also continue with the next
|
||||||
|
// overload, but this seems highly likely to be a caller mistake rather than a
|
||||||
|
// legitimate overload).
|
||||||
|
if (kwargs_in && args_copied < it->args.size()) {
|
||||||
|
handle value = PyDict_GetItemString(kwargs_in, it->args[args_copied].name);
|
||||||
|
if (value)
|
||||||
|
throw type_error(std::string(it->name) + "(): got multiple values for argument '" +
|
||||||
|
std::string(it->args[args_copied].name) + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
int arg_ctr = 0;
|
pass_args.push_back(PyTuple_GET_ITEM(args_in, args_copied));
|
||||||
for (auto const &it2 : it->args) {
|
}
|
||||||
int index = arg_ctr++;
|
|
||||||
if (PyTuple_GET_ITEM(args_.ptr(), index))
|
// We'll need to copy this if we steal some kwargs for defaults
|
||||||
continue;
|
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) {
|
||||||
|
bool copied_kwargs = false;
|
||||||
|
|
||||||
|
for (; args_copied < pos_args; ++args_copied) {
|
||||||
|
const auto &arg = it->args[args_copied];
|
||||||
|
|
||||||
handle value;
|
handle value;
|
||||||
if (kwargs)
|
if (kwargs_in)
|
||||||
value = PyDict_GetItemString(kwargs, it2.name);
|
value = PyDict_GetItemString(kwargs.ptr(), arg.name);
|
||||||
|
|
||||||
if (value)
|
|
||||||
kwargs_consumed++;
|
|
||||||
else if (it2.value)
|
|
||||||
value = it2.value;
|
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
PyTuple_SET_ITEM(args_.ptr(), index, value.inc_ref().ptr());
|
// Consume a kwargs value
|
||||||
} else {
|
if (!copied_kwargs) {
|
||||||
kwargs_consumed = (size_t) -1; /* definite failure */
|
kwargs = reinterpret_steal<dict>(PyDict_Copy(kwargs.ptr()));
|
||||||
break;
|
copied_kwargs = true;
|
||||||
}
|
}
|
||||||
|
PyDict_DelItemString(kwargs.ptr(), arg.name);
|
||||||
}
|
}
|
||||||
|
else if (arg.value) {
|
||||||
|
value = arg.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
pass_args.push_back(value);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args_copied < pos_args)
|
||||||
|
continue; // Not enough arguments, defaults, or kwargs to fill the positional arguments
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Check everything was consumed (unless we have a kwargs arg)
|
||||||
|
if (kwargs && kwargs.size() > 0 && !it->has_kwargs)
|
||||||
|
continue; // Unconsumed kwargs, but no py::kwargs argument to accept them
|
||||||
|
|
||||||
|
// 4a. If we have a py::args argument, create a new tuple with leftovers
|
||||||
|
tuple extra_args;
|
||||||
|
if (it->has_args) {
|
||||||
|
if (args_to_copy == 0) {
|
||||||
|
// We didn't copy out any position arguments from the args_in tuple, so we
|
||||||
|
// can reuse it directly without copying:
|
||||||
|
extra_args = reinterpret_borrow<tuple>(args_in);
|
||||||
|
}
|
||||||
|
else if (args_copied >= n_args_in) {
|
||||||
|
extra_args = tuple(0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
size_t args_size = n_args_in - args_copied;
|
||||||
|
extra_args = tuple(args_size);
|
||||||
|
for (size_t i = 0; i < args_size; ++i) {
|
||||||
|
handle item = PyTuple_GET_ITEM(args_in, args_copied + i);
|
||||||
|
extra_args[i] = item.inc_ref().ptr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pass_args.push_back(extra_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4b. If we have a py::kwargs, pass on any remaining kwargs
|
||||||
|
if (it->has_kwargs) {
|
||||||
|
if (!kwargs.ptr())
|
||||||
|
kwargs = dict(); // If we didn't get one, send an empty one
|
||||||
|
pass_args.push_back(kwargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Put everything in a big tuple. Not technically step 5, we've been building it
|
||||||
|
// in `pass_args` all along.
|
||||||
|
|
||||||
|
// 6. Call the function.
|
||||||
try {
|
try {
|
||||||
if ((kwargs_consumed == nkwargs || it->has_kwargs) &&
|
result = it->impl(it, pass_args, parent);
|
||||||
(nargs_ == it->nargs || it->has_args))
|
|
||||||
result = it->impl(it, args_, kwargs, parent);
|
|
||||||
} catch (reference_cast_error &) {
|
} catch (reference_cast_error &) {
|
||||||
result = PYBIND11_TRY_NEXT_OVERLOAD;
|
result = PYBIND11_TRY_NEXT_OVERLOAD;
|
||||||
}
|
}
|
||||||
@ -512,7 +591,7 @@ protected:
|
|||||||
msg += "\n";
|
msg += "\n";
|
||||||
}
|
}
|
||||||
msg += "\nInvoked with: ";
|
msg += "\nInvoked with: ";
|
||||||
auto args_ = reinterpret_borrow<tuple>(args);
|
auto args_ = reinterpret_borrow<tuple>(args_in);
|
||||||
for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) {
|
for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) {
|
||||||
msg += pybind11::repr(args_[ti]);
|
msg += pybind11::repr(args_[ti]);
|
||||||
if ((ti + 1) != args_.size() )
|
if ((ti + 1) != args_.size() )
|
||||||
@ -530,9 +609,8 @@ protected:
|
|||||||
if (overloads->is_constructor) {
|
if (overloads->is_constructor) {
|
||||||
/* When a constructor ran successfully, the corresponding
|
/* When a constructor ran successfully, the corresponding
|
||||||
holder type (e.g. std::unique_ptr) must still be initialized. */
|
holder type (e.g. std::unique_ptr) must still be initialized. */
|
||||||
PyObject *inst = PyTuple_GET_ITEM(args, 0);
|
auto tinfo = detail::get_type_info(Py_TYPE(parent.ptr()));
|
||||||
auto tinfo = detail::get_type_info(Py_TYPE(inst));
|
tinfo->init_holder(parent.ptr(), nullptr);
|
||||||
tinfo->init_holder(inst, nullptr);
|
|
||||||
}
|
}
|
||||||
return result.ptr();
|
return result.ptr();
|
||||||
}
|
}
|
||||||
@ -1416,11 +1494,11 @@ inline void keep_alive_impl(handle nurse, handle patient) {
|
|||||||
(void) wr.release();
|
(void) wr.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
PYBIND11_NOINLINE inline void keep_alive_impl(int Nurse, int Patient, handle args, handle ret) {
|
PYBIND11_NOINLINE inline void keep_alive_impl(int Nurse, int Patient, function_arguments args, handle ret) {
|
||||||
handle nurse (Nurse > 0 ? PyTuple_GetItem(args.ptr(), Nurse - 1) : ret.ptr());
|
keep_alive_impl(
|
||||||
handle patient(Patient > 0 ? PyTuple_GetItem(args.ptr(), Patient - 1) : ret.ptr());
|
Nurse == 0 ? ret : Nurse > 0 && (size_t) Nurse <= args.size() ? args[Nurse - 1] : handle(),
|
||||||
|
Patient == 0 ? ret : Patient > 0 && (size_t) Patient <= args.size() ? args[Patient - 1] : handle()
|
||||||
keep_alive_impl(nurse, patient);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Iterator, typename Sentinel, bool KeyIterator, return_value_policy Policy>
|
template <typename Iterator, typename Sentinel, bool KeyIterator, return_value_policy Policy>
|
||||||
|
@ -149,14 +149,14 @@ public:
|
|||||||
preferable to use the `object` class which derives from `handle` and calls
|
preferable to use the `object` class which derives from `handle` and calls
|
||||||
this function automatically. Returns a reference to itself.
|
this function automatically. Returns a reference to itself.
|
||||||
\endrst */
|
\endrst */
|
||||||
const handle& inc_ref() const { Py_XINCREF(m_ptr); return *this; }
|
const handle& inc_ref() const & { Py_XINCREF(m_ptr); return *this; }
|
||||||
|
|
||||||
/** \rst
|
/** \rst
|
||||||
Manually decrease the reference count of the Python object. Usually, it is
|
Manually decrease the reference count of the Python object. Usually, it is
|
||||||
preferable to use the `object` class which derives from `handle` and calls
|
preferable to use the `object` class which derives from `handle` and calls
|
||||||
this function automatically. Returns a reference to itself.
|
this function automatically. Returns a reference to itself.
|
||||||
\endrst */
|
\endrst */
|
||||||
const handle& dec_ref() const { Py_XDECREF(m_ptr); return *this; }
|
const handle& dec_ref() const & { Py_XDECREF(m_ptr); return *this; }
|
||||||
|
|
||||||
/** \rst
|
/** \rst
|
||||||
Attempt to cast the Python object into the given C++ type. A `cast_error`
|
Attempt to cast the Python object into the given C++ type. A `cast_error`
|
||||||
|
@ -28,6 +28,27 @@ py::tuple args_kwargs_function(py::args args, py::kwargs kwargs) {
|
|||||||
return py::make_tuple(args, kwargs);
|
return py::make_tuple(args, kwargs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
py::tuple mixed_plus_args(int i, double j, py::args args) {
|
||||||
|
return py::make_tuple(i, j, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
py::tuple mixed_plus_kwargs(int i, double j, py::kwargs kwargs) {
|
||||||
|
return py::make_tuple(i, j, kwargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
py::tuple mixed_plus_args_kwargs(int i, double j, py::args args, py::kwargs kwargs) {
|
||||||
|
return py::make_tuple(i, j, args, kwargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pybind11 won't allow these to be bound: args and kwargs, if present, must be at the end.
|
||||||
|
void bad_args1(py::args, int) {}
|
||||||
|
void bad_args2(py::kwargs, int) {}
|
||||||
|
void bad_args3(py::kwargs, py::args) {}
|
||||||
|
void bad_args4(py::args, int, py::kwargs) {}
|
||||||
|
void bad_args5(py::args, py::kwargs, int) {}
|
||||||
|
void bad_args6(py::args, py::args) {}
|
||||||
|
void bad_args7(py::kwargs, py::kwargs) {}
|
||||||
|
|
||||||
struct KWClass {
|
struct KWClass {
|
||||||
void foo(int, float) {}
|
void foo(int, float) {}
|
||||||
};
|
};
|
||||||
@ -53,4 +74,20 @@ test_initializer arg_keywords_and_defaults([](py::module &m) {
|
|||||||
py::class_<KWClass>(m, "KWClass")
|
py::class_<KWClass>(m, "KWClass")
|
||||||
.def("foo0", &KWClass::foo)
|
.def("foo0", &KWClass::foo)
|
||||||
.def("foo1", &KWClass::foo, "x"_a, "y"_a);
|
.def("foo1", &KWClass::foo, "x"_a, "y"_a);
|
||||||
|
|
||||||
|
m.def("mixed_plus_args", &mixed_plus_args);
|
||||||
|
m.def("mixed_plus_kwargs", &mixed_plus_kwargs);
|
||||||
|
m.def("mixed_plus_args_kwargs", &mixed_plus_args_kwargs);
|
||||||
|
|
||||||
|
m.def("mixed_plus_args_kwargs_defaults", &mixed_plus_args_kwargs,
|
||||||
|
py::arg("i") = 1, py::arg("j") = 3.14159);
|
||||||
|
|
||||||
|
// Uncomment these to test that the static_assert is indeed working:
|
||||||
|
// m.def("bad_args1", &bad_args1);
|
||||||
|
// m.def("bad_args2", &bad_args2);
|
||||||
|
// m.def("bad_args3", &bad_args3);
|
||||||
|
// m.def("bad_args4", &bad_args4);
|
||||||
|
// m.def("bad_args5", &bad_args5);
|
||||||
|
// m.def("bad_args6", &bad_args6);
|
||||||
|
// m.def("bad_args7", &bad_args7);
|
||||||
});
|
});
|
||||||
|
@ -55,3 +55,52 @@ def test_arg_and_kwargs():
|
|||||||
args = 'a1', 'a2'
|
args = 'a1', 'a2'
|
||||||
kwargs = dict(arg3='a3', arg4=4)
|
kwargs = dict(arg3='a3', arg4=4)
|
||||||
assert args_kwargs_function(*args, **kwargs) == (args, kwargs)
|
assert args_kwargs_function(*args, **kwargs) == (args, kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mixed_args_and_kwargs(msg):
|
||||||
|
from pybind11_tests import (mixed_plus_args, mixed_plus_kwargs, mixed_plus_args_kwargs,
|
||||||
|
mixed_plus_args_kwargs_defaults)
|
||||||
|
mpa = mixed_plus_args
|
||||||
|
mpk = mixed_plus_kwargs
|
||||||
|
mpak = mixed_plus_args_kwargs
|
||||||
|
mpakd = mixed_plus_args_kwargs_defaults
|
||||||
|
|
||||||
|
assert mpa(1, 2.5, 4, 99.5, None) == (1, 2.5, (4, 99.5, None))
|
||||||
|
assert mpa(1, 2.5) == (1, 2.5, ())
|
||||||
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
assert mpa(1)
|
||||||
|
assert msg(excinfo.value) == """
|
||||||
|
mixed_plus_args(): incompatible function arguments. The following argument types are supported:
|
||||||
|
1. (arg0: int, arg1: float, *args) -> tuple
|
||||||
|
|
||||||
|
Invoked with: 1
|
||||||
|
""" # noqa: E501
|
||||||
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
assert mpa()
|
||||||
|
assert msg(excinfo.value) == """
|
||||||
|
mixed_plus_args(): incompatible function arguments. The following argument types are supported:
|
||||||
|
1. (arg0: int, arg1: float, *args) -> tuple
|
||||||
|
|
||||||
|
Invoked with:
|
||||||
|
""" # noqa: E501
|
||||||
|
|
||||||
|
assert mpk(-2, 3.5, pi=3.14159, e=2.71828) == (-2, 3.5, {'e': 2.71828, 'pi': 3.14159})
|
||||||
|
assert mpak(7, 7.7, 7.77, 7.777, 7.7777, minusseven=-7) == (
|
||||||
|
7, 7.7, (7.77, 7.777, 7.7777), {'minusseven': -7})
|
||||||
|
assert mpakd() == (1, 3.14159, (), {})
|
||||||
|
assert mpakd(3) == (3, 3.14159, (), {})
|
||||||
|
assert mpakd(j=2.71828) == (1, 2.71828, (), {})
|
||||||
|
assert mpakd(k=42) == (1, 3.14159, (), {'k': 42})
|
||||||
|
assert mpakd(1, 1, 2, 3, 5, 8, then=13, followedby=21) == (
|
||||||
|
1, 1, (2, 3, 5, 8), {'then': 13, 'followedby': 21})
|
||||||
|
# Arguments specified both positionally and via kwargs is an error:
|
||||||
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
assert mpakd(1, i=1)
|
||||||
|
assert msg(excinfo.value) == """
|
||||||
|
mixed_plus_args_kwargs_defaults(): got multiple values for argument 'i'
|
||||||
|
"""
|
||||||
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
assert mpakd(1, 2, j=1)
|
||||||
|
assert msg(excinfo.value) == """
|
||||||
|
mixed_plus_args_kwargs_defaults(): got multiple values for argument 'j'
|
||||||
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user