mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-21 20:55:11 +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")
|
||||
.def(py::init<>())
|
||||
|
||||
.. _implicit_conversions:
|
||||
|
||||
Implicit conversions
|
||||
====================
|
||||
|
||||
|
@ -318,3 +318,57 @@ like so:
|
||||
|
||||
py::class_<MyClass>("MyClass")
|
||||
.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 <typename... Args> struct init;
|
||||
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);
|
||||
|
||||
/// Internal data structure which holds metadata about a keyword argument
|
||||
@ -77,9 +76,10 @@ struct argument_record {
|
||||
const char *name; ///< Argument name
|
||||
const char *descr; ///< Human-readable version of the argument value
|
||||
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)
|
||||
: name(name), descr(descr), value(value) { }
|
||||
argument_record(const char *name, const char *descr, handle value, bool convert)
|
||||
: name(name), descr(descr), value(value), convert(convert) { }
|
||||
};
|
||||
|
||||
/// Internal data structure which holds metadata about a bound function (signature, overloads, etc.)
|
||||
@ -131,7 +131,7 @@ struct function_record {
|
||||
bool is_method : 1;
|
||||
|
||||
/// Number of arguments (including py::args and/or py::kwargs, if present)
|
||||
uint16_t nargs;
|
||||
std::uint16_t nargs;
|
||||
|
||||
/// Python method object
|
||||
PyMethodDef *def = nullptr;
|
||||
@ -222,21 +222,11 @@ struct type_record {
|
||||
}
|
||||
};
|
||||
|
||||
/// Internal data associated with a single function call
|
||||
struct function_call {
|
||||
function_call(function_record &f, handle p) : func(f), parent(p) {
|
||||
args.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;
|
||||
};
|
||||
inline function_call::function_call(function_record &f, handle p) :
|
||||
func(f), parent(p) {
|
||||
args.reserve(f.nargs);
|
||||
args_convert.reserve(f.nargs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
static void init(const arg &a, function_record *r) {
|
||||
if (r->is_method && r->args.empty())
|
||||
r->args.emplace_back("self", nullptr, handle());
|
||||
r->args.emplace_back(a.name, nullptr, handle());
|
||||
r->args.emplace_back("self", nullptr, handle(), true /*convert*/);
|
||||
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> {
|
||||
static void init(const arg_v &a, function_record *r) {
|
||||
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 !defined(NDEBUG)
|
||||
@ -330,7 +320,7 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
|
||||
"Compile in debug mode for more information.");
|
||||
#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>
|
||||
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;
|
||||
typedef typename std::conditional<std::is_signed<T>::value, _py_type_0, typename std::make_unsigned<_py_type_0>::type>::type _py_type_1;
|
||||
typedef typename std::conditional<std::is_floating_point<T>::value, double, _py_type_1>::type py_type;
|
||||
using _py_type_0 = conditional_t<sizeof(T) <= sizeof(long), long, long long>;
|
||||
using _py_type_1 = conditional_t<std::is_signed<T>::value, _py_type_0, typename std::make_unsigned<_py_type_0>::type>;
|
||||
using py_type = conditional_t<std::is_floating_point<T>::value, double, _py_type_1>;
|
||||
public:
|
||||
|
||||
bool load(handle src, bool) {
|
||||
bool load(handle src, bool convert) {
|
||||
py_type py_value;
|
||||
|
||||
if (!src) {
|
||||
if (!src)
|
||||
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)) {
|
||||
if (PyFloat_Check(src.ptr()))
|
||||
return false;
|
||||
@ -511,7 +515,7 @@ public:
|
||||
bool type_error = PyErr_ExceptionMatches(PyExc_TypeError);
|
||||
#endif
|
||||
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
|
||||
? PyNumber_Float(src.ptr())
|
||||
: PyNumber_Long(src.ptr()));
|
||||
@ -1198,22 +1202,26 @@ template <return_value_policy policy = return_value_policy::automatic_reference,
|
||||
}
|
||||
|
||||
/// \ingroup annotations
|
||||
/// Annotation for keyword arguments
|
||||
/// Annotation for arguments
|
||||
struct arg {
|
||||
/// Set the name of the argument
|
||||
constexpr explicit arg(const char *name) : name(name) { }
|
||||
/// Constructs an argument with the name of the argument; if null or omitted, this is a positional argument.
|
||||
constexpr explicit arg(const char *name = nullptr) : name(name), flag_noconvert(false) { }
|
||||
/// Assign a value to this argument
|
||||
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
|
||||
/// Annotation for keyword arguments with values
|
||||
/// Annotation for arguments with values
|
||||
struct arg_v : arg {
|
||||
private:
|
||||
template <typename T>
|
||||
arg_v(const char *name, T &&x, const char *descr = nullptr)
|
||||
: arg(name),
|
||||
arg_v(arg &&base, T &&x, const char *descr = nullptr)
|
||||
: arg(base),
|
||||
value(reinterpret_steal<object>(
|
||||
detail::make_caster<T>::cast(x, return_value_policy::automatic, {})
|
||||
)),
|
||||
@ -1223,15 +1231,32 @@ struct arg_v : arg {
|
||||
#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;
|
||||
/// The (optional) description of the default value
|
||||
const char *descr;
|
||||
#if !defined(NDEBUG)
|
||||
/// The C++ type name of the default value (only available when compiled in debug mode)
|
||||
std::string type;
|
||||
#endif
|
||||
};
|
||||
|
||||
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
|
||||
template <typename /*unused*/> using arg_t = arg_v;
|
||||
@ -1248,11 +1273,28 @@ NAMESPACE_BEGIN(detail)
|
||||
// forward declaration
|
||||
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
|
||||
template <typename... Args>
|
||||
class argument_loader {
|
||||
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_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()...); }
|
||||
|
||||
bool load_args(function_arguments args) {
|
||||
return load_impl_sequence(args, indices{});
|
||||
bool load_args(function_call &call) {
|
||||
return load_impl_sequence(call, indices{});
|
||||
}
|
||||
|
||||
template <typename Return, typename Func>
|
||||
@ -1287,11 +1329,11 @@ public:
|
||||
|
||||
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>
|
||||
bool load_impl_sequence(function_arguments args, index_sequence<Is...>) {
|
||||
for (bool r : {std::get<Is>(value).load(args[Is], true)...})
|
||||
bool load_impl_sequence(function_call &call, index_sequence<Is...>) {
|
||||
for (bool r : {std::get<Is>(value).load(call.args[Is], call.args_convert[Is])...})
|
||||
if (!r)
|
||||
return false;
|
||||
return true;
|
||||
@ -1380,6 +1422,13 @@ private:
|
||||
}
|
||||
|
||||
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 defined(NDEBUG)
|
||||
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() {
|
||||
throw type_error("Got multiple values for keyword argument "
|
||||
"(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>> {
|
||||
public:
|
||||
bool load(handle src, bool) {
|
||||
bool load(handle src, bool convert) {
|
||||
if (!src)
|
||||
return false;
|
||||
if (!convert && !PyComplex_Check(src.ptr()))
|
||||
return false;
|
||||
Py_complex result = PyComplex_AsCComplex(src.ptr());
|
||||
if (result.real == -1.0 && PyErr_Occurred()) {
|
||||
PyErr_Clear();
|
||||
|
@ -122,7 +122,7 @@ protected:
|
||||
cast_in args_converter;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* Invoke call policy pre-call hook */
|
||||
@ -198,7 +198,7 @@ protected:
|
||||
if (c == '{') {
|
||||
// Write arg name for everything except *args, **kwargs and return type.
|
||||
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;
|
||||
} else if (arg_index == 0 && rec->is_method) {
|
||||
signature += "self";
|
||||
@ -257,7 +257,7 @@ protected:
|
||||
rec->signature = strdup(signature.c_str());
|
||||
rec->args.shrink_to_fit();
|
||||
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 (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,
|
||||
result = PYBIND11_TRY_NEXT_OVERLOAD;
|
||||
|
||||
try {
|
||||
for (; it != nullptr; it = it->next) {
|
||||
|
||||
/* For each overload:
|
||||
1. Copy all positional arguments we were given, also checking to make sure that
|
||||
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
|
||||
// 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 (kwargs_in && args_copied < func.args.size() && func.args[args_copied].name) {
|
||||
handle value = PyDict_GetItemString(kwargs_in, func.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) + "'");
|
||||
throw type_error(std::string(func.name) + "(): got multiple values for argument '" +
|
||||
std::string(func.args[args_copied].name) + "'");
|
||||
}
|
||||
|
||||
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
|
||||
@ -453,10 +456,10 @@ protected:
|
||||
bool copied_kwargs = false;
|
||||
|
||||
for (; args_copied < pos_args; ++args_copied) {
|
||||
const auto &arg = it->args[args_copied];
|
||||
const auto &arg = func.args[args_copied];
|
||||
|
||||
handle value;
|
||||
if (kwargs_in)
|
||||
if (kwargs_in && arg.name)
|
||||
value = PyDict_GetItemString(kwargs.ptr(), arg.name);
|
||||
|
||||
if (value) {
|
||||
@ -470,8 +473,10 @@ protected:
|
||||
value = arg.value;
|
||||
}
|
||||
|
||||
if (value)
|
||||
if (value) {
|
||||
call.args.push_back(value);
|
||||
call.args_convert.push_back(arg.convert);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
@ -481,12 +486,12 @@ protected:
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 4a. If we have a py::args argument, create a new tuple with leftovers
|
||||
tuple extra_args;
|
||||
if (it->has_args) {
|
||||
if (func.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:
|
||||
@ -502,31 +507,34 @@ protected:
|
||||
}
|
||||
}
|
||||
call.args.push_back(extra_args);
|
||||
call.args_convert.push_back(false);
|
||||
}
|
||||
|
||||
// 4b. If we have a py::kwargs, pass on any remaining kwargs
|
||||
if (it->has_kwargs) {
|
||||
if (func.has_kwargs) {
|
||||
if (!kwargs.ptr())
|
||||
kwargs = dict(); // If we didn't get one, send an empty one
|
||||
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
|
||||
// in `call.args` all along.
|
||||
#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!");
|
||||
#endif
|
||||
|
||||
// 6. Call the function.
|
||||
try {
|
||||
result = it->impl(call);
|
||||
result = func.impl(call);
|
||||
} catch (reference_cast_error &) {
|
||||
result = PYBIND11_TRY_NEXT_OVERLOAD;
|
||||
}
|
||||
|
||||
if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD)
|
||||
break;
|
||||
|
||||
}
|
||||
} catch (error_already_set &e) {
|
||||
e.restore();
|
||||
|
@ -97,6 +97,42 @@ public:
|
||||
|
||||
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) {
|
||||
py::class_<ExampleMandA>(m, "ExampleMandA")
|
||||
.def(py::init<>())
|
||||
@ -183,4 +219,25 @@ test_initializer methods_and_attributes([](py::module &m) {
|
||||
py::class_<CppDerivedDynamicClass, DynamicClass>(m, "CppDerivedDynamicClass")
|
||||
.def(py::init());
|
||||
#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
|
||||
del i1, i2
|
||||
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