Merge pull request #634 from jagerman/noconvert-arguments

Add support for non-converting arguments
This commit is contained in:
Wenzel Jakob 2017-02-05 23:51:38 +01:00 committed by GitHub
commit 18e34cb2e6
8 changed files with 276 additions and 61 deletions

View File

@ -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
==================== ====================

View File

@ -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()``.

View File

@ -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);
} }
}; };

View File

@ -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)");

View File

@ -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();

View File

@ -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();

View File

@ -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());
}); });

View File

@ -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
"""