From 178c8a899da094f391f937fbb927786c756440f4 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Tue, 10 May 2016 15:59:01 +0100 Subject: [PATCH] nicer type_caster::load() calling conventions --- docs/advanced.rst | 54 +++++++++++++++++++++++++- docs/release.rst | 2 +- example/example11.cpp | 21 +++++++++- example/example11.py | 6 ++- example/example11.ref | 7 ++++ include/pybind11/attr.h | 13 ++++++- include/pybind11/cast.h | 76 ++++++++++++++++++++++++++++--------- include/pybind11/common.h | 2 +- include/pybind11/complex.h | 2 + include/pybind11/eigen.h | 3 ++ include/pybind11/pybind11.h | 40 +++++++++++-------- include/pybind11/pytypes.h | 58 ++++++++++++++-------------- 12 files changed, 213 insertions(+), 71 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 901faa73f..e83ba2734 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -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(); 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(); + +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") .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 ================================================= diff --git a/docs/release.rst b/docs/release.rst index 4c3ff72f3..d8f9db854 100644 --- a/docs/release.rst +++ b/docs/release.rst @@ -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 diff --git a/example/example11.cpp b/example/example11.cpp index 5a91ee298..466eed465 100644 --- a/example/example11.cpp +++ b/example/example11.cpp @@ -19,11 +19,25 @@ void kw_func4(const std::vector &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); } diff --git a/example/example11.py b/example/example11.py index 948b0fd7c..ff35be2e4 100755 --- a/example/example11.py +++ b/example/example11.py @@ -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) diff --git a/example/example11.ref b/example/example11.ref index 0ce066b9d..4c433b75e 100644 --- a/example/example11.ref +++ b/example/example11.ref @@ -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 diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 8aa428216..d63a44e9f 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -92,7 +92,7 @@ struct function_record { std::vector 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; diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 4ee79d204..add186f7c 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -15,6 +15,7 @@ #include "descr.h" #include #include +#include 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::value) { + if (!src) { + return false; + } if (std::is_floating_point::value) { py_value = (py_type) PyFloat_AsDouble(src.ptr()); } else if (sizeof(T) <= sizeof(long)) { if (std::is_signed::value) @@ -394,7 +397,9 @@ public: using type_caster::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 : public type_caster { template <> class type_caster { 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 : public type_caster { public: bool load(handle src, bool convert) { - if (src.ptr() == Py_None) { return true; } + if (src.ptr() == Py_None) return true; return type_caster::load(src, convert); } @@ -550,7 +560,7 @@ public: template <> class type_caster : public type_caster { public: bool load(handle src, bool convert) { - if (src.ptr() == Py_None) { return true; } + if (src.ptr() == Py_None) return true; return type_caster::load(src, convert); } @@ -574,7 +584,9 @@ template class type_caster> { typedef std::pair 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 class type_caster> { typedef std::tuple type; + typedef std::tuple::type...> itype; + typedef std::tuple args_type; + typedef std::tuple args_kwargs_type; public: enum { size = sizeof...(Tuple) }; + static constexpr const bool has_kwargs = std::is_same::value; + static constexpr const bool has_args = has_kwargs || std::is_same::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::type()); } + template ::value && + !std::is_same::value, int>::type = 0> + bool load_args(handle args, handle, bool convert) { + return load(args, convert, typename make_index_sequence::type()); + } + + template ::value, int>::type = 0> + bool load_args(handle args, handle, bool convert) { + std::get<0>(value).load(args, convert); + return true; + } + + template ::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::type()); } @@ -655,10 +695,8 @@ protected: } template bool load(handle src, bool convert, index_sequence) { - if (!PyTuple_Check(src.ptr()) || PyTuple_Size(src.ptr()) != size) - return false; std::array success {{ - (PyTuple_GET_ITEM(src.ptr(), Indices) != nullptr ? std::get(value).load(PyTuple_GET_ITEM(src.ptr(), Indices), convert) : false)... + std::get(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 struct handle_type_name { static PYBIND11_DESCR name() { return _(); } }; template <> struct handle_type_name { static PYBIND11_DESCR name() { return _(PYBIND11_BYTES_NAME); } }; +template <> struct handle_type_name { static PYBIND11_DESCR name() { return _("*args"); } }; +template <> struct handle_type_name { static PYBIND11_DESCR name() { return _("**kwargs"); } }; template struct type_caster::value>::type> { public: - template ::value, int>::type = 0> - bool load(handle src, bool /* convert */) { value = src; return value.check(); } + template ::value, int>::type = 0> + bool load(handle src, bool /* convert */) { value = type(src); return value.check(); } - template ::value, int>::type = 0> + template ::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 T cast(handle handle) { return conv.operator typename type_caster::template cast_op_type(); } -template object cast(const T &value, return_value_policy policy = return_value_policy::automatic_reference, handle parent = handle()) { +template 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::value ? return_value_policy::take_ownership : return_value_policy::copy; else if (policy == return_value_policy::automatic_reference) @@ -808,14 +850,14 @@ template object handle::call(Args &&... args) const { return operator()(std::forward(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(); diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 401a84bad..38f8bc8c8 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -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 diff --git a/include/pybind11/complex.h b/include/pybind11/complex.h index ba0b29745..f767f354c 100644 --- a/include/pybind11/complex.h +++ b/include/pybind11/complex.h @@ -26,6 +26,8 @@ NAMESPACE_BEGIN(detail) template class type_caster> { 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(); diff --git a/include/pybind11/eigen.h b/include/pybind11/eigen.h index fa61a1188..746f0a2f7 100644 --- a/include/pybind11/eigen.h +++ b/include/pybind11/eigen.h @@ -138,6 +138,9 @@ struct type_caster::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( diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index e7e07d609..bd0eb5090 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -92,11 +92,11 @@ protected: typename detail::intrinsic_type::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(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); } diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 99fb5034e..c8f1cac2a 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -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 T cast() const; - template + template [[deprecated("call(...) was deprecated in favor of operator()(...)")]] object call(Args&&... args) const; template 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(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(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)