mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 06:35:12 +00:00
Merge pull request #634 from jagerman/noconvert-arguments
Add support for non-converting arguments
This commit is contained in:
commit
18e34cb2e6
@ -388,6 +388,8 @@ crucial that instances are deallocated on the C++ side to avoid memory leaks.
|
|||||||
py::class_<MyClass, std::unique_ptr<MyClass, py::nodelete>>(m, "MyClass")
|
py::class_<MyClass, std::unique_ptr<MyClass, py::nodelete>>(m, "MyClass")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
|
|
||||||
|
.. _implicit_conversions:
|
||||||
|
|
||||||
Implicit conversions
|
Implicit conversions
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
@ -318,3 +318,57 @@ like so:
|
|||||||
|
|
||||||
py::class_<MyClass>("MyClass")
|
py::class_<MyClass>("MyClass")
|
||||||
.def("myFunction", py::arg("arg") = (SomeType *) nullptr);
|
.def("myFunction", py::arg("arg") = (SomeType *) nullptr);
|
||||||
|
|
||||||
|
Non-converting arguments
|
||||||
|
========================
|
||||||
|
|
||||||
|
Certain argument types may support conversion from one type to another. Some
|
||||||
|
examples of conversions are:
|
||||||
|
|
||||||
|
* :ref:`implicit_conversions` declared using ``py::implicitly_convertible<A,B>()``
|
||||||
|
* Calling a method accepting a double with an integer argument
|
||||||
|
* Calling a ``std::complex<float>`` argument with a non-complex python type
|
||||||
|
(for example, with a float). (Requires the optional ``pybind11/complex.h``
|
||||||
|
header).
|
||||||
|
* Calling a function taking an Eigen matrix reference with a numpy array of the
|
||||||
|
wrong type or of an incompatible data layout. (Requires the optional
|
||||||
|
``pybind11/eigen.h`` header).
|
||||||
|
|
||||||
|
This behaviour is sometimes undesirable: the binding code may prefer to raise
|
||||||
|
an error rather than convert the argument. This behaviour can be obtained
|
||||||
|
through ``py::arg`` by calling the ``.noconvert()`` method of the ``py::arg``
|
||||||
|
object, such as:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
m.def("floats_only", [](double f) { return 0.5 * f; }, py::arg("f").noconvert());
|
||||||
|
m.def("floats_preferred", [](double f) { return 0.5 * f; }, py::arg("f"));
|
||||||
|
|
||||||
|
Attempting the call the second function (the one without ``.noconvert()``) with
|
||||||
|
an integer will succeed, but attempting to call the ``.noconvert()`` version
|
||||||
|
will fail with a ``TypeError``:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> floats_preferred(4)
|
||||||
|
2.0
|
||||||
|
>>> floats_only(4)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<stdin>", line 1, in <module>
|
||||||
|
TypeError: floats_only(): incompatible function arguments. The following argument types are supported:
|
||||||
|
1. (f: float) -> float
|
||||||
|
|
||||||
|
Invoked with: 4
|
||||||
|
|
||||||
|
You may, of course, combine this with the :var:`_a` shorthand notation (see
|
||||||
|
:ref:`keyword_args`) and/or :ref:`default_args`. It is also permitted to omit
|
||||||
|
the argument name by using the ``py::arg()`` constructor without an argument
|
||||||
|
name, i.e. by specifying ``py::arg().noconvert()``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When specifying ``py::arg`` options it is necessary to provide the same
|
||||||
|
number of options as the bound function has arguments. Thus if you want to
|
||||||
|
enable no-convert behaviour for just one of several arguments, you will
|
||||||
|
need to specify a ``py::arg()`` annotation for each argument with the
|
||||||
|
no-convert argument modified to ``py::arg().noconvert()``.
|
||||||
|
@ -69,7 +69,6 @@ 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;
|
||||||
struct function_call;
|
|
||||||
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
|
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
|
||||||
|
|
||||||
/// Internal data structure which holds metadata about a keyword argument
|
/// Internal data structure which holds metadata about a keyword argument
|
||||||
@ -77,9 +76,10 @@ struct argument_record {
|
|||||||
const char *name; ///< Argument name
|
const char *name; ///< Argument name
|
||||||
const char *descr; ///< Human-readable version of the argument value
|
const char *descr; ///< Human-readable version of the argument value
|
||||||
handle value; ///< Associated Python object
|
handle value; ///< Associated Python object
|
||||||
|
bool convert : 1; ///< True if the argument is allowed to convert when loading
|
||||||
|
|
||||||
argument_record(const char *name, const char *descr, handle value)
|
argument_record(const char *name, const char *descr, handle value, bool convert)
|
||||||
: name(name), descr(descr), value(value) { }
|
: name(name), descr(descr), value(value), convert(convert) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Internal data structure which holds metadata about a bound function (signature, overloads, etc.)
|
/// Internal data structure which holds metadata about a bound function (signature, overloads, etc.)
|
||||||
@ -131,7 +131,7 @@ struct function_record {
|
|||||||
bool is_method : 1;
|
bool is_method : 1;
|
||||||
|
|
||||||
/// Number of arguments (including py::args and/or py::kwargs, if present)
|
/// Number of arguments (including py::args and/or py::kwargs, if present)
|
||||||
uint16_t nargs;
|
std::uint16_t nargs;
|
||||||
|
|
||||||
/// Python method object
|
/// Python method object
|
||||||
PyMethodDef *def = nullptr;
|
PyMethodDef *def = nullptr;
|
||||||
@ -222,21 +222,11 @@ struct type_record {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Internal data associated with a single function call
|
inline function_call::function_call(function_record &f, handle p) :
|
||||||
struct function_call {
|
func(f), parent(p) {
|
||||||
function_call(function_record &f, handle p) : func(f), parent(p) {
|
args.reserve(f.nargs);
|
||||||
args.reserve(f.nargs);
|
args_convert.reserve(f.nargs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The function data:
|
|
||||||
const function_record &func;
|
|
||||||
|
|
||||||
/// Arguments passed to the function:
|
|
||||||
std::vector<handle> args;
|
|
||||||
|
|
||||||
/// The parent, if any
|
|
||||||
handle parent;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Partial template specializations to process custom attributes provided to
|
* Partial template specializations to process custom attributes provided to
|
||||||
@ -300,8 +290,8 @@ template <> struct process_attribute<is_operator> : process_attribute_default<is
|
|||||||
template <> struct process_attribute<arg> : process_attribute_default<arg> {
|
template <> struct process_attribute<arg> : process_attribute_default<arg> {
|
||||||
static void init(const arg &a, function_record *r) {
|
static void init(const arg &a, function_record *r) {
|
||||||
if (r->is_method && r->args.empty())
|
if (r->is_method && r->args.empty())
|
||||||
r->args.emplace_back("self", nullptr, handle());
|
r->args.emplace_back("self", nullptr, handle(), true /*convert*/);
|
||||||
r->args.emplace_back(a.name, nullptr, handle());
|
r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -309,7 +299,7 @@ template <> struct process_attribute<arg> : process_attribute_default<arg> {
|
|||||||
template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
|
template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
|
||||||
static void init(const arg_v &a, function_record *r) {
|
static void init(const arg_v &a, function_record *r) {
|
||||||
if (r->is_method && r->args.empty())
|
if (r->is_method && r->args.empty())
|
||||||
r->args.emplace_back("self", nullptr, handle());
|
r->args.emplace_back("self", nullptr /*descr*/, handle() /*parent*/, true /*convert*/);
|
||||||
|
|
||||||
if (!a.value) {
|
if (!a.value) {
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
@ -330,7 +320,7 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
|
|||||||
"Compile in debug mode for more information.");
|
"Compile in debug mode for more information.");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
r->args.emplace_back(a.name, a.descr, a.value.inc_ref());
|
r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -473,18 +473,22 @@ public:
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct type_caster<T, enable_if_t<std::is_arithmetic<T>::value>> {
|
struct type_caster<T, enable_if_t<std::is_arithmetic<T>::value>> {
|
||||||
typedef typename std::conditional<sizeof(T) <= sizeof(long), long, long long>::type _py_type_0;
|
using _py_type_0 = conditional_t<sizeof(T) <= sizeof(long), long, long long>;
|
||||||
typedef typename std::conditional<std::is_signed<T>::value, _py_type_0, typename std::make_unsigned<_py_type_0>::type>::type _py_type_1;
|
using _py_type_1 = conditional_t<std::is_signed<T>::value, _py_type_0, typename std::make_unsigned<_py_type_0>::type>;
|
||||||
typedef typename std::conditional<std::is_floating_point<T>::value, double, _py_type_1>::type py_type;
|
using py_type = conditional_t<std::is_floating_point<T>::value, double, _py_type_1>;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
bool load(handle src, bool) {
|
bool load(handle src, bool convert) {
|
||||||
py_type py_value;
|
py_type py_value;
|
||||||
|
|
||||||
if (!src) {
|
if (!src)
|
||||||
return false;
|
return false;
|
||||||
} if (std::is_floating_point<T>::value) {
|
|
||||||
py_value = (py_type) PyFloat_AsDouble(src.ptr());
|
if (std::is_floating_point<T>::value) {
|
||||||
|
if (convert || PyFloat_Check(src.ptr()))
|
||||||
|
py_value = (py_type) PyFloat_AsDouble(src.ptr());
|
||||||
|
else
|
||||||
|
return false;
|
||||||
} else if (sizeof(T) <= sizeof(long)) {
|
} else if (sizeof(T) <= sizeof(long)) {
|
||||||
if (PyFloat_Check(src.ptr()))
|
if (PyFloat_Check(src.ptr()))
|
||||||
return false;
|
return false;
|
||||||
@ -511,7 +515,7 @@ public:
|
|||||||
bool type_error = PyErr_ExceptionMatches(PyExc_TypeError);
|
bool type_error = PyErr_ExceptionMatches(PyExc_TypeError);
|
||||||
#endif
|
#endif
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
if (type_error && PyNumber_Check(src.ptr())) {
|
if (type_error && convert && PyNumber_Check(src.ptr())) {
|
||||||
auto tmp = reinterpret_borrow<object>(std::is_floating_point<T>::value
|
auto tmp = reinterpret_borrow<object>(std::is_floating_point<T>::value
|
||||||
? PyNumber_Float(src.ptr())
|
? PyNumber_Float(src.ptr())
|
||||||
: PyNumber_Long(src.ptr()));
|
: PyNumber_Long(src.ptr()));
|
||||||
@ -1198,22 +1202,26 @@ template <return_value_policy policy = return_value_policy::automatic_reference,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// \ingroup annotations
|
/// \ingroup annotations
|
||||||
/// Annotation for keyword arguments
|
/// Annotation for arguments
|
||||||
struct arg {
|
struct arg {
|
||||||
/// Set the name of the argument
|
/// Constructs an argument with the name of the argument; if null or omitted, this is a positional argument.
|
||||||
constexpr explicit arg(const char *name) : name(name) { }
|
constexpr explicit arg(const char *name = nullptr) : name(name), flag_noconvert(false) { }
|
||||||
/// Assign a value to this argument
|
/// Assign a value to this argument
|
||||||
template <typename T> arg_v operator=(T &&value) const;
|
template <typename T> arg_v operator=(T &&value) const;
|
||||||
|
/// Indicate that the type should not be converted in the type caster
|
||||||
|
arg &noconvert(bool flag = true) { flag_noconvert = flag; return *this; }
|
||||||
|
|
||||||
const char *name;
|
const char *name; ///< If non-null, this is a named kwargs argument
|
||||||
|
bool flag_noconvert : 1; ///< If set, do not allow conversion (requires a supporting type caster!)
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \ingroup annotations
|
/// \ingroup annotations
|
||||||
/// Annotation for keyword arguments with values
|
/// Annotation for arguments with values
|
||||||
struct arg_v : arg {
|
struct arg_v : arg {
|
||||||
|
private:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
arg_v(const char *name, T &&x, const char *descr = nullptr)
|
arg_v(arg &&base, T &&x, const char *descr = nullptr)
|
||||||
: arg(name),
|
: arg(base),
|
||||||
value(reinterpret_steal<object>(
|
value(reinterpret_steal<object>(
|
||||||
detail::make_caster<T>::cast(x, return_value_policy::automatic, {})
|
detail::make_caster<T>::cast(x, return_value_policy::automatic, {})
|
||||||
)),
|
)),
|
||||||
@ -1223,15 +1231,32 @@ struct arg_v : arg {
|
|||||||
#endif
|
#endif
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Direct construction with name, default, and description
|
||||||
|
template <typename T>
|
||||||
|
arg_v(const char *name, T &&x, const char *descr = nullptr)
|
||||||
|
: arg_v(arg(name), std::forward<T>(x), descr) { }
|
||||||
|
|
||||||
|
/// Called internally when invoking `py::arg("a") = value`
|
||||||
|
template <typename T>
|
||||||
|
arg_v(const arg &base, T &&x, const char *descr = nullptr)
|
||||||
|
: arg_v(arg(base), std::forward<T>(x), descr) { }
|
||||||
|
|
||||||
|
/// Same as `arg::noconvert()`, but returns *this as arg_v&, not arg&
|
||||||
|
arg_v &noconvert(bool flag = true) { arg::noconvert(flag); return *this; }
|
||||||
|
|
||||||
|
/// The default value
|
||||||
object value;
|
object value;
|
||||||
|
/// The (optional) description of the default value
|
||||||
const char *descr;
|
const char *descr;
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
|
/// The C++ type name of the default value (only available when compiled in debug mode)
|
||||||
std::string type;
|
std::string type;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
arg_v arg::operator=(T &&value) const { return {name, std::forward<T>(value)}; }
|
arg_v arg::operator=(T &&value) const { return {std::move(*this), std::forward<T>(value)}; }
|
||||||
|
|
||||||
/// Alias for backward compatibility -- to be removed in version 2.0
|
/// Alias for backward compatibility -- to be removed in version 2.0
|
||||||
template <typename /*unused*/> using arg_t = arg_v;
|
template <typename /*unused*/> using arg_t = arg_v;
|
||||||
@ -1248,11 +1273,28 @@ NAMESPACE_BEGIN(detail)
|
|||||||
// forward declaration
|
// forward declaration
|
||||||
struct function_record;
|
struct function_record;
|
||||||
|
|
||||||
|
/// Internal data associated with a single function call
|
||||||
|
struct function_call {
|
||||||
|
function_call(function_record &f, handle p); // Implementation in attr.h
|
||||||
|
|
||||||
|
/// The function data:
|
||||||
|
const function_record &func;
|
||||||
|
|
||||||
|
/// Arguments passed to the function:
|
||||||
|
std::vector<handle> args;
|
||||||
|
|
||||||
|
/// The `convert` value the arguments should be loaded with
|
||||||
|
std::vector<bool> args_convert;
|
||||||
|
|
||||||
|
/// The parent, if any
|
||||||
|
handle parent;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/// 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 indices = make_index_sequence<sizeof...(Args)>;
|
using indices = make_index_sequence<sizeof...(Args)>;
|
||||||
using function_arguments = const std::vector<handle> &;
|
|
||||||
|
|
||||||
template <typename Arg> using argument_is_args = std::is_same<intrinsic_t<Arg>, args>;
|
template <typename Arg> using argument_is_args = std::is_same<intrinsic_t<Arg>, args>;
|
||||||
template <typename Arg> using argument_is_kwargs = std::is_same<intrinsic_t<Arg>, kwargs>;
|
template <typename Arg> using argument_is_kwargs = std::is_same<intrinsic_t<Arg>, kwargs>;
|
||||||
@ -1270,8 +1312,8 @@ public:
|
|||||||
|
|
||||||
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(function_arguments args) {
|
bool load_args(function_call &call) {
|
||||||
return load_impl_sequence(args, indices{});
|
return load_impl_sequence(call, indices{});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Return, typename Func>
|
template <typename Return, typename Func>
|
||||||
@ -1287,11 +1329,11 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static bool load_impl_sequence(function_arguments, index_sequence<>) { return true; }
|
static bool load_impl_sequence(function_call &, index_sequence<>) { return true; }
|
||||||
|
|
||||||
template <size_t... Is>
|
template <size_t... Is>
|
||||||
bool load_impl_sequence(function_arguments args, index_sequence<Is...>) {
|
bool load_impl_sequence(function_call &call, index_sequence<Is...>) {
|
||||||
for (bool r : {std::get<Is>(value).load(args[Is], true)...})
|
for (bool r : {std::get<Is>(value).load(call.args[Is], call.args_convert[Is])...})
|
||||||
if (!r)
|
if (!r)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
@ -1380,6 +1422,13 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void process(list &/*args_list*/, arg_v a) {
|
void process(list &/*args_list*/, arg_v a) {
|
||||||
|
if (!a.name)
|
||||||
|
#if defined(NDEBUG)
|
||||||
|
nameless_argument_error();
|
||||||
|
#else
|
||||||
|
nameless_argument_error(a.type);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (m_kwargs.contains(a.name)) {
|
if (m_kwargs.contains(a.name)) {
|
||||||
#if defined(NDEBUG)
|
#if defined(NDEBUG)
|
||||||
multiple_values_error();
|
multiple_values_error();
|
||||||
@ -1412,6 +1461,15 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[noreturn]] static void nameless_argument_error() {
|
||||||
|
throw type_error("Got kwargs without a name; only named arguments "
|
||||||
|
"may be passed via py::arg() to a python function call. "
|
||||||
|
"(compile in debug mode for details)");
|
||||||
|
}
|
||||||
|
[[noreturn]] static void nameless_argument_error(std::string type) {
|
||||||
|
throw type_error("Got kwargs without a name of type '" + type + "'; only named "
|
||||||
|
"arguments may be passed via py::arg() to a python function call. ");
|
||||||
|
}
|
||||||
[[noreturn]] static void multiple_values_error() {
|
[[noreturn]] static void multiple_values_error() {
|
||||||
throw type_error("Got multiple values for keyword argument "
|
throw type_error("Got multiple values for keyword argument "
|
||||||
"(compile in debug mode for details)");
|
"(compile in debug mode for details)");
|
||||||
|
@ -28,9 +28,11 @@ template <typename T> struct is_fmt_numeric<std::complex<T>> {
|
|||||||
|
|
||||||
template <typename T> class type_caster<std::complex<T>> {
|
template <typename T> class type_caster<std::complex<T>> {
|
||||||
public:
|
public:
|
||||||
bool load(handle src, bool) {
|
bool load(handle src, bool convert) {
|
||||||
if (!src)
|
if (!src)
|
||||||
return false;
|
return false;
|
||||||
|
if (!convert && !PyComplex_Check(src.ptr()))
|
||||||
|
return false;
|
||||||
Py_complex result = PyComplex_AsCComplex(src.ptr());
|
Py_complex result = PyComplex_AsCComplex(src.ptr());
|
||||||
if (result.real == -1.0 && PyErr_Occurred()) {
|
if (result.real == -1.0 && PyErr_Occurred()) {
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
|
@ -122,7 +122,7 @@ protected:
|
|||||||
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(call.args))
|
if (!args_converter.load_args(call))
|
||||||
return PYBIND11_TRY_NEXT_OVERLOAD;
|
return PYBIND11_TRY_NEXT_OVERLOAD;
|
||||||
|
|
||||||
/* Invoke call policy pre-call hook */
|
/* Invoke call policy pre-call hook */
|
||||||
@ -198,7 +198,7 @@ protected:
|
|||||||
if (c == '{') {
|
if (c == '{') {
|
||||||
// Write arg name for everything except *args, **kwargs and return type.
|
// Write arg name for everything except *args, **kwargs and return type.
|
||||||
if (type_depth == 0 && text[char_index] != '*' && arg_index < args) {
|
if (type_depth == 0 && text[char_index] != '*' && arg_index < args) {
|
||||||
if (!rec->args.empty()) {
|
if (!rec->args.empty() && rec->args[arg_index].name) {
|
||||||
signature += rec->args[arg_index].name;
|
signature += rec->args[arg_index].name;
|
||||||
} else if (arg_index == 0 && rec->is_method) {
|
} else if (arg_index == 0 && rec->is_method) {
|
||||||
signature += "self";
|
signature += "self";
|
||||||
@ -257,7 +257,7 @@ protected:
|
|||||||
rec->signature = strdup(signature.c_str());
|
rec->signature = strdup(signature.c_str());
|
||||||
rec->args.shrink_to_fit();
|
rec->args.shrink_to_fit();
|
||||||
rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__");
|
rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__");
|
||||||
rec->nargs = (uint16_t) args;
|
rec->nargs = (std::uint16_t) args;
|
||||||
|
|
||||||
#if PY_MAJOR_VERSION < 3
|
#if PY_MAJOR_VERSION < 3
|
||||||
if (rec->sibling && PyMethod_Check(rec->sibling.ptr()))
|
if (rec->sibling && PyMethod_Check(rec->sibling.ptr()))
|
||||||
@ -392,8 +392,10 @@ protected:
|
|||||||
|
|
||||||
handle parent = n_args_in > 0 ? PyTuple_GET_ITEM(args_in, 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) {
|
||||||
|
|
||||||
/* For each overload:
|
/* For each overload:
|
||||||
1. Copy all positional arguments we were given, also checking to make sure that
|
1. Copy all positional arguments we were given, also checking to make sure that
|
||||||
named positional arguments weren't *also* specified via kwarg.
|
named positional arguments weren't *also* specified via kwarg.
|
||||||
@ -435,14 +437,15 @@ protected:
|
|||||||
// raise a TypeError like Python does. (We could also continue with the next
|
// 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
|
// overload, but this seems highly likely to be a caller mistake rather than a
|
||||||
// legitimate overload).
|
// legitimate overload).
|
||||||
if (kwargs_in && args_copied < it->args.size()) {
|
if (kwargs_in && args_copied < func.args.size() && func.args[args_copied].name) {
|
||||||
handle value = PyDict_GetItemString(kwargs_in, it->args[args_copied].name);
|
handle value = PyDict_GetItemString(kwargs_in, func.args[args_copied].name);
|
||||||
if (value)
|
if (value)
|
||||||
throw type_error(std::string(it->name) + "(): got multiple values for argument '" +
|
throw type_error(std::string(func.name) + "(): got multiple values for argument '" +
|
||||||
std::string(it->args[args_copied].name) + "'");
|
std::string(func.args[args_copied].name) + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
call.args.push_back(PyTuple_GET_ITEM(args_in, args_copied));
|
call.args.push_back(PyTuple_GET_ITEM(args_in, args_copied));
|
||||||
|
call.args_convert.push_back(args_copied < func.args.size() ? func.args[args_copied].convert : true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll need to copy this if we steal some kwargs for defaults
|
// We'll need to copy this if we steal some kwargs for defaults
|
||||||
@ -453,10 +456,10 @@ protected:
|
|||||||
bool copied_kwargs = false;
|
bool copied_kwargs = false;
|
||||||
|
|
||||||
for (; args_copied < pos_args; ++args_copied) {
|
for (; args_copied < pos_args; ++args_copied) {
|
||||||
const auto &arg = it->args[args_copied];
|
const auto &arg = func.args[args_copied];
|
||||||
|
|
||||||
handle value;
|
handle value;
|
||||||
if (kwargs_in)
|
if (kwargs_in && arg.name)
|
||||||
value = PyDict_GetItemString(kwargs.ptr(), arg.name);
|
value = PyDict_GetItemString(kwargs.ptr(), arg.name);
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
@ -470,8 +473,10 @@ protected:
|
|||||||
value = arg.value;
|
value = arg.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value)
|
if (value) {
|
||||||
call.args.push_back(value);
|
call.args.push_back(value);
|
||||||
|
call.args_convert.push_back(arg.convert);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -481,12 +486,12 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check everything was consumed (unless we have a kwargs arg)
|
// 3. Check everything was consumed (unless we have a kwargs arg)
|
||||||
if (kwargs && kwargs.size() > 0 && !it->has_kwargs)
|
if (kwargs && kwargs.size() > 0 && !func.has_kwargs)
|
||||||
continue; // Unconsumed kwargs, but no py::kwargs argument to accept them
|
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
|
// 4a. If we have a py::args argument, create a new tuple with leftovers
|
||||||
tuple extra_args;
|
tuple extra_args;
|
||||||
if (it->has_args) {
|
if (func.has_args) {
|
||||||
if (args_to_copy == 0) {
|
if (args_to_copy == 0) {
|
||||||
// We didn't copy out any position arguments from the args_in tuple, so we
|
// We didn't copy out any position arguments from the args_in tuple, so we
|
||||||
// can reuse it directly without copying:
|
// can reuse it directly without copying:
|
||||||
@ -502,31 +507,34 @@ protected:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
call.args.push_back(extra_args);
|
call.args.push_back(extra_args);
|
||||||
|
call.args_convert.push_back(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4b. If we have a py::kwargs, pass on any remaining kwargs
|
// 4b. If we have a py::kwargs, pass on any remaining kwargs
|
||||||
if (it->has_kwargs) {
|
if (func.has_kwargs) {
|
||||||
if (!kwargs.ptr())
|
if (!kwargs.ptr())
|
||||||
kwargs = dict(); // If we didn't get one, send an empty one
|
kwargs = dict(); // If we didn't get one, send an empty one
|
||||||
call.args.push_back(kwargs);
|
call.args.push_back(kwargs);
|
||||||
|
call.args_convert.push_back(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Put everything in a vector. Not technically step 5, we've been building it
|
// 5. Put everything in a vector. Not technically step 5, we've been building it
|
||||||
// in `call.args` all along.
|
// in `call.args` all along.
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
if (call.args.size() != call.func.nargs)
|
if (call.args.size() != func.nargs || call.args_convert.size() != func.nargs)
|
||||||
pybind11_fail("Internal error: function call dispatcher inserted wrong number of arguments!");
|
pybind11_fail("Internal error: function call dispatcher inserted wrong number of arguments!");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 6. Call the function.
|
// 6. Call the function.
|
||||||
try {
|
try {
|
||||||
result = it->impl(call);
|
result = func.impl(call);
|
||||||
} catch (reference_cast_error &) {
|
} catch (reference_cast_error &) {
|
||||||
result = PYBIND11_TRY_NEXT_OVERLOAD;
|
result = PYBIND11_TRY_NEXT_OVERLOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD)
|
if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (error_already_set &e) {
|
} catch (error_already_set &e) {
|
||||||
e.restore();
|
e.restore();
|
||||||
|
@ -97,6 +97,42 @@ public:
|
|||||||
|
|
||||||
class CppDerivedDynamicClass : public DynamicClass { };
|
class CppDerivedDynamicClass : public DynamicClass { };
|
||||||
|
|
||||||
|
// py::arg/py::arg_v testing: these arguments just record their argument when invoked
|
||||||
|
class ArgInspector1 { public: std::string arg = "(default arg inspector 1)"; };
|
||||||
|
class ArgInspector2 { public: std::string arg = "(default arg inspector 2)"; };
|
||||||
|
namespace pybind11 { namespace detail {
|
||||||
|
template <> struct type_caster<ArgInspector1> {
|
||||||
|
public:
|
||||||
|
PYBIND11_TYPE_CASTER(ArgInspector1, _("ArgInspector1"));
|
||||||
|
|
||||||
|
bool load(handle src, bool convert) {
|
||||||
|
value.arg = "loading ArgInspector1 argument " +
|
||||||
|
std::string(convert ? "WITH" : "WITHOUT") + " conversion allowed. "
|
||||||
|
"Argument value = " + (std::string) str(src);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static handle cast(const ArgInspector1 &src, return_value_policy, handle) {
|
||||||
|
return str(src.arg).release();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template <> struct type_caster<ArgInspector2> {
|
||||||
|
public:
|
||||||
|
PYBIND11_TYPE_CASTER(ArgInspector2, _("ArgInspector2"));
|
||||||
|
|
||||||
|
bool load(handle src, bool convert) {
|
||||||
|
value.arg = "loading ArgInspector2 argument " +
|
||||||
|
std::string(convert ? "WITH" : "WITHOUT") + " conversion allowed. "
|
||||||
|
"Argument value = " + (std::string) str(src);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static handle cast(const ArgInspector2 &src, return_value_policy, handle) {
|
||||||
|
return str(src.arg).release();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
|
||||||
test_initializer methods_and_attributes([](py::module &m) {
|
test_initializer methods_and_attributes([](py::module &m) {
|
||||||
py::class_<ExampleMandA>(m, "ExampleMandA")
|
py::class_<ExampleMandA>(m, "ExampleMandA")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
@ -183,4 +219,25 @@ test_initializer methods_and_attributes([](py::module &m) {
|
|||||||
py::class_<CppDerivedDynamicClass, DynamicClass>(m, "CppDerivedDynamicClass")
|
py::class_<CppDerivedDynamicClass, DynamicClass>(m, "CppDerivedDynamicClass")
|
||||||
.def(py::init());
|
.def(py::init());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
class ArgInspector {
|
||||||
|
public:
|
||||||
|
ArgInspector1 f(ArgInspector1 a) { return a; }
|
||||||
|
std::string g(ArgInspector1 a, const ArgInspector1 &b, int c, ArgInspector2 *d) {
|
||||||
|
return a.arg + "\n" + b.arg + "\n" + std::to_string(c) + "\n" + d->arg;
|
||||||
|
}
|
||||||
|
static ArgInspector2 h(ArgInspector2 a) { return a; }
|
||||||
|
};
|
||||||
|
py::class_<ArgInspector>(m, "ArgInspector")
|
||||||
|
.def(py::init<>())
|
||||||
|
.def("f", &ArgInspector::f)
|
||||||
|
.def("g", &ArgInspector::g, "a"_a.noconvert(), "b"_a, "c"_a.noconvert()=13, "d"_a=ArgInspector2())
|
||||||
|
.def_static("h", &ArgInspector::h, py::arg().noconvert())
|
||||||
|
;
|
||||||
|
m.def("arg_inspect_func", [](ArgInspector2 a, ArgInspector1 b) { return a.arg + "\n" + b.arg; },
|
||||||
|
py::arg().noconvert(false), py::arg_v(nullptr, ArgInspector1()).noconvert(true));
|
||||||
|
|
||||||
|
m.def("floats_preferred", [](double f) { return 0.5 * f; }, py::arg("f"));
|
||||||
|
m.def("floats_only", [](double f) { return 0.5 * f; }, py::arg("f").noconvert());
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -203,3 +203,47 @@ def test_cyclic_gc():
|
|||||||
assert cstats.alive() == 2
|
assert cstats.alive() == 2
|
||||||
del i1, i2
|
del i1, i2
|
||||||
assert cstats.alive() == 0
|
assert cstats.alive() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_noconvert_args(msg):
|
||||||
|
from pybind11_tests import ArgInspector, arg_inspect_func, floats_only, floats_preferred
|
||||||
|
|
||||||
|
a = ArgInspector()
|
||||||
|
assert msg(a.f("hi")) == """
|
||||||
|
loading ArgInspector1 argument WITH conversion allowed. Argument value = hi
|
||||||
|
"""
|
||||||
|
assert msg(a.g("this is a", "this is b")) == """
|
||||||
|
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = this is a
|
||||||
|
loading ArgInspector1 argument WITH conversion allowed. Argument value = this is b
|
||||||
|
13
|
||||||
|
loading ArgInspector2 argument WITH conversion allowed. Argument value = (default arg inspector 2)
|
||||||
|
""" # noqa: E501 line too long
|
||||||
|
assert msg(a.g("this is a", "this is b", 42)) == """
|
||||||
|
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = this is a
|
||||||
|
loading ArgInspector1 argument WITH conversion allowed. Argument value = this is b
|
||||||
|
42
|
||||||
|
loading ArgInspector2 argument WITH conversion allowed. Argument value = (default arg inspector 2)
|
||||||
|
""" # noqa: E501 line too long
|
||||||
|
assert msg(a.g("this is a", "this is b", 42, "this is d")) == """
|
||||||
|
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = this is a
|
||||||
|
loading ArgInspector1 argument WITH conversion allowed. Argument value = this is b
|
||||||
|
42
|
||||||
|
loading ArgInspector2 argument WITH conversion allowed. Argument value = this is d
|
||||||
|
"""
|
||||||
|
assert (a.h("arg 1") ==
|
||||||
|
"loading ArgInspector2 argument WITHOUT conversion allowed. Argument value = arg 1")
|
||||||
|
assert msg(arg_inspect_func("A1", "A2")) == """
|
||||||
|
loading ArgInspector2 argument WITH conversion allowed. Argument value = A1
|
||||||
|
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = A2
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert floats_preferred(4) == 2.0
|
||||||
|
assert floats_only(4.0) == 2.0
|
||||||
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
floats_only(4)
|
||||||
|
assert msg(excinfo.value) == """
|
||||||
|
floats_only(): incompatible function arguments. The following argument types are supported:
|
||||||
|
1. (f: float) -> float
|
||||||
|
|
||||||
|
Invoked with: 4
|
||||||
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user