nicer type_caster::load() calling conventions

This commit is contained in:
Wenzel Jakob 2016-05-10 15:59:01 +01:00
parent 5984baafd0
commit 178c8a899d
12 changed files with 213 additions and 71 deletions

View File

@ -903,7 +903,7 @@ its popularity and widespread adoption, pybind11 provides transparent
conversion support between Eigen and Scientific Python linear algebra data types.
Specifically, when including the optional header file :file:`pybind11/eigen.h`,
pybind11 will automatically and transparently convert
pybind11 will automatically and transparently convert
1. Static and dynamic Eigen dense vectors and matrices to instances of
``numpy.ndarray`` (and vice versa).
@ -1250,11 +1250,31 @@ The reverse direction uses the following syntax:
MyClass *cls = obj.cast<MyClass *>();
When conversion fails, both directions throw the exception :class:`cast_error`.
It is also possible to call python functions via ``operator()``.
.. code-block:: cpp
py::function f = <...>;
py::object result_py = f(1234, "hello", some_instance);
MyClass &result = result_py.cast<MyClass>();
The special ``f(*args)`` and ``f(*args, **kwargs)`` syntax is also supported to
supply arbitrary argument and keyword lists, although these cannot be mixed
with other parameters.
.. code-block:: cpp
py::function f = <...>;
py::tuple args = py::make_tuple(1234);
py::dict kwargs;
kwargs["y"] = py::cast(5678);
py::object result = f(*args, **kwargs);
.. seealso::
The file :file:`example/example2.cpp` contains a complete example that
demonstrates passing native Python types in more detail.
demonstrates passing native Python types in more detail. The file
:file:`example/example11.cpp` discusses usage of ``args`` and ``kwargs``.
Default arguments revisited
===========================
@ -1303,6 +1323,36 @@ like so:
py::class_<MyClass>("MyClass")
.def("myFunction", py::arg("arg") = (SomeType *) nullptr);
Binding functions that accept arbitrary numbers of arguments and keywords arguments
===================================================================================
Python provides a useful mechanism to define functions that accept arbitrary
numbers of arguments and keyword arguments:
.. code-block:: cpp
def generic(*args, **kwargs):
# .. do something with args and kwargs
Such functions can also be created using pybind11:
.. code-block:: cpp
void generic(py::args args, py::kwargs kwargs) {
/// .. do something with args
if (kwargs)
/// .. do something with kwargs
}
/// Binding code
m.def("generic", &generic);
(See ``example/example11.cpp``). The class ``py::args`` derives from
``py::list`` and ``py::kwargs`` derives from ``py::dict`` Note that the
``kwargs`` argument is invalid if no keyword 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.
Partitioning code over multiple extension modules
=================================================

View File

@ -9,7 +9,7 @@ To release a new version of pybind11:
- ``git push --tags``.
- ``python setup.py sdist upload``.
- ``python setup.py bdist_wheel upload``.
- Update conda-forge
- Update conda-forge (https://github.com/conda-forge/pybind11-feedstock)
- change version number in ``meta.yml``
- update checksum to match the one computed by pypi
- Get back to work

View File

@ -19,11 +19,25 @@ void kw_func4(const std::vector<int> &entries) {
std::cout << endl;
}
void call_kw_func(py::function f) {
py::object call_kw_func(py::function f) {
py::tuple args = py::make_tuple(1234);
py::dict kwargs;
kwargs["y"] = py::cast(5678);
f(*args, **kwargs);
return f(*args, **kwargs);
}
void args_function(py::args args) {
for (auto item : args)
std::cout << "got argument: " << item << std::endl;
}
void args_kwargs_function(py::args args, py::kwargs kwargs) {
for (auto item : args)
std::cout << "got argument: " << item << std::endl;
if (kwargs) {
for (auto item : kwargs)
std::cout << "got keyword argument: " << item.first << " -> " << item.second << std::endl;
}
}
void init_ex11(py::module &m) {
@ -38,4 +52,7 @@ void init_ex11(py::module &m) {
m.def("kw_func4", &kw_func4, py::arg("myList") = list);
m.def("call_kw_func", &call_kw_func);
m.def("args_function", &args_function);
m.def("args_kwargs_function", &args_kwargs_function);
}

View File

@ -6,6 +6,7 @@ import pydoc
sys.path.append('.')
from example import kw_func, kw_func2, kw_func3, kw_func4, call_kw_func
from example import args_function, args_kwargs_function
print(pydoc.render_doc(kw_func, "Help on %s"))
print(pydoc.render_doc(kw_func2, "Help on %s"))
@ -32,6 +33,9 @@ except Exception as e:
print("Caught expected exception: " + str(e))
kw_func4()
kw_func4(myList = [1, 2, 3])
kw_func4(myList=[1, 2, 3])
call_kw_func(kw_func2)
args_function('arg1_value', 'arg2_value', 3)
args_kwargs_function('arg1_value', 'arg2_value', arg3='arg3_value', arg4=4)

View File

@ -33,3 +33,10 @@ Caught expected exception: Incompatible function arguments. The following argume
kw_func4: 13 17
kw_func4: 1 2 3
kw_func(x=1234, y=5678)
got argument: arg1_value
got argument: arg2_value
got argument: 3
got argument: arg1_value
got argument: arg2_value
got keyword argument: arg3 -> arg3_value
got keyword argument: arg4 -> 4

View File

@ -92,7 +92,7 @@ struct function_record {
std::vector<argument_record> args;
/// Pointer to lambda function which converts arguments and performs the actual call
handle (*impl) (function_record *, handle, handle) = nullptr;
handle (*impl) (function_record *, handle, handle, handle) = nullptr;
/// Storage for the wrapped function pointer and captured data, if any
void *data[3] = { };
@ -104,7 +104,16 @@ struct function_record {
return_value_policy policy = return_value_policy::automatic;
/// True if name == '__init__'
bool is_constructor = false;
bool is_constructor : 1;
/// True if the function has a '*args' argument
bool has_args : 1;
/// True if the function has a '**kwargs' argument
bool has_kwargs : 1;
/// Number of arguments
uint16_t nargs;
/// Python method object
PyMethodDef *def = nullptr;

View File

@ -15,6 +15,7 @@
#include "descr.h"
#include <array>
#include <limits>
#include <iostream>
NAMESPACE_BEGIN(pybind11)
NAMESPACE_BEGIN(detail)
@ -320,7 +321,9 @@ public:
bool load(handle src, bool) {
py_type py_value;
if (std::is_floating_point<T>::value) {
if (!src) {
return false;
} if (std::is_floating_point<T>::value) {
py_value = (py_type) PyFloat_AsDouble(src.ptr());
} else if (sizeof(T) <= sizeof(long)) {
if (std::is_signed<T>::value)
@ -394,7 +397,9 @@ public:
using type_caster<void_type>::cast;
bool load(handle h, bool) {
if (h.ptr() == Py_None) {
if (!h) {
return false;
} else if (h.ptr() == Py_None) {
value = nullptr;
return true;
}
@ -435,7 +440,8 @@ template <> class type_caster<std::nullptr_t> : public type_caster<void_type> {
template <> class type_caster<bool> {
public:
bool load(handle src, bool) {
if (src.ptr() == Py_True) { value = true; return true; }
if (!src) return false;
else if (src.ptr() == Py_True) { value = true; return true; }
else if (src.ptr() == Py_False) { value = false; return true; }
else return false;
}
@ -450,7 +456,9 @@ public:
bool load(handle src, bool) {
object temp;
handle load_src = src;
if (PyUnicode_Check(load_src.ptr())) {
if (!src) {
return false;
} else if (PyUnicode_Check(load_src.ptr())) {
temp = object(PyUnicode_AsUTF8String(load_src.ptr()), false);
if (!temp) { PyErr_Clear(); return false; } // UnicodeEncodeError
load_src = temp;
@ -489,7 +497,9 @@ public:
bool load(handle src, bool) {
object temp;
handle load_src = src;
if (!PyUnicode_Check(load_src.ptr())) {
if (!src) {
return false;
} else if (!PyUnicode_Check(load_src.ptr())) {
temp = object(PyUnicode_FromObject(load_src.ptr()), false);
if (!temp) { PyErr_Clear(); return false; }
load_src = temp;
@ -527,7 +537,7 @@ protected:
template <> class type_caster<char> : public type_caster<std::string> {
public:
bool load(handle src, bool convert) {
if (src.ptr() == Py_None) { return true; }
if (src.ptr() == Py_None) return true;
return type_caster<std::string>::load(src, convert);
}
@ -550,7 +560,7 @@ public:
template <> class type_caster<wchar_t> : public type_caster<std::wstring> {
public:
bool load(handle src, bool convert) {
if (src.ptr() == Py_None) { return true; }
if (src.ptr() == Py_None) return true;
return type_caster<std::wstring>::load(src, convert);
}
@ -574,7 +584,9 @@ template <typename T1, typename T2> class type_caster<std::pair<T1, T2>> {
typedef std::pair<T1, T2> type;
public:
bool load(handle src, bool convert) {
if (!PyTuple_Check(src.ptr()) || PyTuple_Size(src.ptr()) != 2)
if (!src)
return false;
else if (!PyTuple_Check(src.ptr()) || PyTuple_Size(src.ptr()) != 2)
return false;
return first.load(PyTuple_GET_ITEM(src.ptr(), 0), convert) &&
second.load(PyTuple_GET_ITEM(src.ptr(), 1), convert);
@ -610,13 +622,41 @@ protected:
template <typename... Tuple> class type_caster<std::tuple<Tuple...>> {
typedef std::tuple<Tuple...> type;
typedef std::tuple<typename intrinsic_type<Tuple>::type...> itype;
typedef std::tuple<args> args_type;
typedef std::tuple<args, kwargs> args_kwargs_type;
public:
enum { size = sizeof...(Tuple) };
static constexpr const bool has_kwargs = std::is_same<itype, args_kwargs_type>::value;
static constexpr const bool has_args = has_kwargs || std::is_same<itype, args_type>::value;
bool load(handle src, bool convert) {
if (!src || !PyTuple_Check(src.ptr()) || PyTuple_GET_SIZE(src.ptr()) != size)
return false;
return load(src, convert, typename make_index_sequence<sizeof...(Tuple)>::type());
}
template <typename T = itype, typename std::enable_if<
!std::is_same<T, args_type>::value &&
!std::is_same<T, args_kwargs_type>::value, int>::type = 0>
bool load_args(handle args, handle, bool convert) {
return load(args, convert, typename make_index_sequence<sizeof...(Tuple)>::type());
}
template <typename T = itype, typename std::enable_if<std::is_same<T, args_type>::value, int>::type = 0>
bool load_args(handle args, handle, bool convert) {
std::get<0>(value).load(args, convert);
return true;
}
template <typename T = itype, typename std::enable_if<std::is_same<T, args_kwargs_type>::value, int>::type = 0>
bool load_args(handle args, handle kwargs, bool convert) {
std::get<0>(value).load(args, convert);
std::get<1>(value).load(kwargs, convert);
return true;
}
static handle cast(const type &src, return_value_policy policy, handle parent) {
return cast(src, policy, parent, typename make_index_sequence<size>::type());
}
@ -655,10 +695,8 @@ protected:
}
template <size_t ... Indices> bool load(handle src, bool convert, index_sequence<Indices...>) {
if (!PyTuple_Check(src.ptr()) || PyTuple_Size(src.ptr()) != size)
return false;
std::array<bool, size> success {{
(PyTuple_GET_ITEM(src.ptr(), Indices) != nullptr ? std::get<Indices>(value).load(PyTuple_GET_ITEM(src.ptr(), Indices), convert) : false)...
std::get<Indices>(value).load(PyTuple_GET_ITEM(src.ptr(), Indices), convert)...
}};
(void) convert; /* avoid a warning when the tuple is empty */
for (bool r : success)
@ -742,14 +780,16 @@ protected:
template <typename T> struct handle_type_name { static PYBIND11_DESCR name() { return _<T>(); } };
template <> struct handle_type_name<bytes> { static PYBIND11_DESCR name() { return _(PYBIND11_BYTES_NAME); } };
template <> struct handle_type_name<args> { static PYBIND11_DESCR name() { return _("*args"); } };
template <> struct handle_type_name<kwargs> { static PYBIND11_DESCR name() { return _("**kwargs"); } };
template <typename type>
struct type_caster<type, typename std::enable_if<std::is_base_of<handle, type>::value>::type> {
public:
template <typename T = type, typename std::enable_if<std::is_same<T, handle>::value, int>::type = 0>
bool load(handle src, bool /* convert */) { value = src; return value.check(); }
template <typename T = type, typename std::enable_if<!std::is_base_of<object, T>::value, int>::type = 0>
bool load(handle src, bool /* convert */) { value = type(src); return value.check(); }
template <typename T = type, typename std::enable_if<!std::is_same<T, handle>::value, int>::type = 0>
template <typename T = type, typename std::enable_if<std::is_base_of<object, T>::value, int>::type = 0>
bool load(handle src, bool /* convert */) { value = type(src, true); return value.check(); }
static handle cast(const handle &src, return_value_policy /* policy */, handle /* parent */) {
@ -768,7 +808,9 @@ template <typename T> T cast(handle handle) {
return conv.operator typename type_caster::template cast_op_type<T>();
}
template <typename T> object cast(const T &value, return_value_policy policy = return_value_policy::automatic_reference, handle parent = handle()) {
template <typename T> object cast(const T &value,
return_value_policy policy = return_value_policy::automatic_reference,
handle parent = handle()) {
if (policy == return_value_policy::automatic)
policy = std::is_pointer<T>::value ? return_value_policy::take_ownership : return_value_policy::copy;
else if (policy == return_value_policy::automatic_reference)
@ -808,14 +850,14 @@ template <typename... Args> object handle::call(Args &&... args) const {
return operator()(std::forward<Args>(args)...);
}
inline object handle::operator()(detail::args args) const {
inline object handle::operator()(detail::args_proxy args) const {
object result(PyObject_CallObject(m_ptr, args.ptr()), false);
if (!result)
throw error_already_set();
return result;
}
inline object handle::operator()(detail::args args, detail::kwargs kwargs) const {
inline object handle::operator()(detail::args_proxy args, detail::kwargs_proxy kwargs) const {
object result(PyObject_Call(m_ptr, args.ptr(), kwargs.ptr()), false);
if (!result)
throw error_already_set();

View File

@ -142,7 +142,7 @@ NAMESPACE_BEGIN(pybind11)
typedef Py_ssize_t ssize_t;
/// Approach used to cast a previously unknown C++ instance into a Python object
enum class return_value_policy : int {
enum class return_value_policy : uint8_t {
/** This is the default return value policy, which falls back to the policy
return_value_policy::take_ownership when the return value is a pointer.
Otherwise, it uses return_value::move or return_value::copy for rvalue

View File

@ -26,6 +26,8 @@ NAMESPACE_BEGIN(detail)
template <typename T> class type_caster<std::complex<T>> {
public:
bool load(handle src, bool) {
if (!src)
return false;
Py_complex result = PyComplex_AsCComplex(src.ptr());
if (result.real == -1.0 && PyErr_Occurred()) {
PyErr_Clear();

View File

@ -138,6 +138,9 @@ struct type_caster<Type, typename std::enable_if<is_eigen_sparse<Type>::value>::
static constexpr bool rowMajor = Type::Flags & Eigen::RowMajorBit;
bool load(handle src, bool) {
if (!src)
return false;
object obj(src, true);
object sparse_module = module::import("scipy.sparse");
object matrix_type = sparse_module.attr(

View File

@ -92,11 +92,11 @@ protected:
typename detail::intrinsic_type<Return>::type>::type> cast_out;
/* Dispatch code which converts function arguments and performs the actual function call */
rec->impl = [](detail::function_record *rec, handle args, handle parent) -> handle {
rec->impl = [](detail::function_record *rec, handle args, handle kwargs, handle parent) -> handle {
cast_in args_converter;
/* Try to cast the function arguments into the C++ domain */
if (!args_converter.load(args, true))
if (!args_converter.load_args(args, kwargs, true))
return PYBIND11_TRY_NEXT_OVERLOAD;
/* Invoke call policy pre-call hook */
@ -106,7 +106,7 @@ protected:
capture *cap = (capture *) (sizeof(capture) <= sizeof(rec->data)
? &rec->data : rec->data[0]);
/* Perform the functionc all */
/* Perform the functioncall */
handle result = cast_out::cast(args_converter.template call<Return>(cap->f),
rec->policy, parent);
@ -125,6 +125,9 @@ protected:
/* Register the function with Python from generic (non-templated) code */
initialize_generic(rec, signature.text(), signature.types(), sizeof...(Args));
if (cast_in::has_args) rec->has_args = true;
if (cast_in::has_kwargs) rec->has_kwargs = true;
}
/// Register a function call with Python (generic non-templated code goes here)
@ -204,9 +207,12 @@ protected:
std::to_string(args) + " arguments, but " + std::to_string(rec->args.size()) +
" pybind11::arg entries were specified!");
rec->is_constructor = !strcmp(rec->name, "__init__");
rec->signature = strdup(signature.c_str());
rec->args.shrink_to_fit();
rec->is_constructor = !strcmp(rec->name, "__init__");
rec->has_args = false;
rec->has_kwargs = false;
rec->nargs = args;
#if PY_MAJOR_VERSION < 3
if (rec->sibling && PyMethod_Check(rec->sibling.ptr()))
@ -325,15 +331,15 @@ protected:
*it = overloads;
/* Need to know how many arguments + keyword arguments there are to pick the right overload */
int nargs = (int) PyTuple_Size(args),
nkwargs = kwargs ? (int) PyDict_Size(kwargs) : 0;
size_t nargs = PyTuple_GET_SIZE(args),
nkwargs = kwargs ? PyDict_Size(kwargs) : 0;
handle parent = nargs > 0 ? PyTuple_GetItem(args, 0) : nullptr,
handle parent = nargs > 0 ? PyTuple_GET_ITEM(args, 0) : nullptr,
result = PYBIND11_TRY_NEXT_OVERLOAD;
try {
for (; it != nullptr; it = it->next) {
tuple args_(args, true);
int kwargs_consumed = 0;
size_t kwargs_consumed = 0;
/* For each overload:
1. If the required list of arguments is longer than the
@ -342,10 +348,11 @@ protected:
2. Ensure that all keyword arguments were "consumed"
3. Call the function call dispatcher (function_record::impl)
*/
if (nargs < (int) it->args.size()) {
args_ = tuple(it->args.size());
for (int i = 0; i < nargs; ++i) {
size_t nargs_ = nargs;
if (nargs < it->args.size()) {
nargs_ = it->args.size();
args_ = tuple(nargs_);
for (size_t i = 0; i < nargs; ++i) {
handle item = PyTuple_GET_ITEM(args, i);
PyTuple_SET_ITEM(args_.ptr(), i, item.inc_ref().ptr());
}
@ -368,15 +375,16 @@ protected:
if (value) {
PyTuple_SET_ITEM(args_.ptr(), index, value.inc_ref().ptr());
} else {
kwargs_consumed = -1; /* definite failure */
kwargs_consumed = (size_t) -1; /* definite failure */
break;
}
}
}
try {
if (kwargs_consumed == nkwargs)
result = it->impl(it, args_, parent);
if ((kwargs_consumed == nkwargs || it->has_kwargs) &&
(nargs_ == it->nargs || it->has_args))
result = it->impl(it, args_, kwargs, parent);
} catch (cast_error &) {
result = PYBIND11_TRY_NEXT_OVERLOAD;
}
@ -420,7 +428,7 @@ protected:
if (overloads->is_constructor) {
/* When a constructor ran successfully, the corresponding
holder type (e.g. std::unique_ptr) must still be initialized. */
PyObject *inst = PyTuple_GetItem(args, 0);
PyObject *inst = PyTuple_GET_ITEM(args, 0);
auto tinfo = detail::get_type_info(Py_TYPE(inst));
tinfo->init_holder(inst, nullptr);
}

View File

@ -16,12 +16,8 @@
NAMESPACE_BEGIN(pybind11)
/* A few forward declarations */
class object;
class str;
class object;
class dict;
class iterator;
namespace detail { class accessor; class args; class kwargs; }
class object; class str; class object; class dict; class iterator;
namespace detail { class accessor; class args_proxy; class kwargs_proxy; }
/// Holds a reference to a Python object (no reference counting)
class handle {
@ -43,17 +39,17 @@ public:
inline detail::accessor attr(const char *key) const;
inline pybind11::str str() const;
template <typename T> T cast() const;
template <typename ... Args>
template <typename ... Args>
[[deprecated("call(...) was deprecated in favor of operator()(...)")]]
object call(Args&&... args) const;
template <typename ... Args> object operator()(Args&&... args) const;
inline object operator()(detail::args args) const;
inline object operator()(detail::args args, detail::kwargs kwargs) const;
inline object operator()(detail::args_proxy args) const;
inline object operator()(detail::args_proxy f_args, detail::kwargs_proxy kwargs) const;
operator bool() const { return m_ptr != nullptr; }
bool operator==(const handle &h) const { return m_ptr == h.m_ptr; }
bool operator!=(const handle &h) const { return m_ptr != h.m_ptr; }
bool check() const { return m_ptr != nullptr; }
inline detail::args operator*() const;
inline detail::args_proxy operator*() const;
protected:
PyObject *m_ptr;
};
@ -218,17 +214,6 @@ private:
ssize_t pos = 0;
};
class kwargs : public handle {
public:
kwargs(handle h) : handle(h) { }
};
class args : public handle {
public:
args(handle h) : handle(h) { }
kwargs operator*() const { return kwargs(*this); }
};
inline bool PyIterable_Check(PyObject *obj) {
PyObject *iter = PyObject_GetIter(obj);
if (iter) {
@ -239,20 +224,32 @@ inline bool PyIterable_Check(PyObject *obj) {
return false;
}
}
inline bool PyNone_Check(PyObject *o) { return o == Py_None; }
inline bool PyUnicode_Check_Permissive(PyObject *o) { return PyUnicode_Check(o) || PYBIND11_BYTES_CHECK(o); }
class kwargs_proxy : public handle {
public:
kwargs_proxy(handle h) : handle(h) { }
};
class args_proxy : public handle {
public:
args_proxy(handle h) : handle(h) { }
kwargs_proxy operator*() const { return kwargs_proxy(*this); }
};
NAMESPACE_END(detail)
#define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, CvtStmt) \
Name(const handle &h, bool borrowed) : Parent(h, borrowed) { CvtStmt; } \
Name(const object& o): Parent(o) { CvtStmt; } \
Name(object&& o) noexcept : Parent(std::move(o)) { CvtStmt; } \
Name& operator=(object&& o) noexcept { (void) object::operator=(std::move(o)); CvtStmt; return *this; } \
Name& operator=(const object& o) { return static_cast<Name&>(object::operator=(o)); CvtStmt; } \
bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); }
public: \
Name(const handle &h, bool borrowed) : Parent(h, borrowed) { CvtStmt; } \
Name(const object& o): Parent(o) { CvtStmt; } \
Name(object&& o) noexcept : Parent(std::move(o)) { CvtStmt; } \
Name& operator=(object&& o) noexcept { (void) object::operator=(std::move(o)); CvtStmt; return *this; } \
Name& operator=(const object& o) { return static_cast<Name&>(object::operator=(o)); CvtStmt; } \
bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); }
#define PYBIND11_OBJECT(Name, Parent, CheckFun) \
PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, )
@ -332,7 +329,7 @@ inline detail::accessor handle::attr(handle key) const { return detail::accessor
inline detail::accessor handle::attr(const char *key) const { return detail::accessor(ptr(), key, true); }
inline iterator handle::begin() const { return iterator(PyObject_GetIter(ptr()), false); }
inline iterator handle::end() const { return iterator(nullptr, false); }
inline detail::args handle::operator*() const { return detail::args(*this); }
inline detail::args_proxy handle::operator*() const { return detail::args_proxy(*this); }
class str : public object {
public:
@ -520,6 +517,9 @@ public:
void append(const object &object) const { PyList_Append(m_ptr, object.ptr()); }
};
class args : public list { PYBIND11_OBJECT_DEFAULT(args, list, PyList_Check) };
class kwargs : public dict { PYBIND11_OBJECT_DEFAULT(kwargs, dict, PyDict_Check) };
class set : public object {
public:
PYBIND11_OBJECT(set, object, PySet_Check)