From 317524ffad6ac255a6cbceb6e6b95c46ea48c673 Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Sun, 28 Aug 2016 14:32:48 +0200 Subject: [PATCH 01/10] Make arg_t hold a pointer instead of a copy of the value --- include/pybind11/attr.h | 13 ++++--------- include/pybind11/pytypes.h | 1 + 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 7925b8e2..ba4dd841 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -14,18 +14,12 @@ NAMESPACE_BEGIN(pybind11) -template struct arg_t; - /// Annotation for keyword arguments struct arg { constexpr explicit arg(const char *name) : name(name) { } template constexpr arg_t operator=(const T &value) const { return {name, value}; } - template - constexpr arg_t operator=(T const (&value)[N]) const { - return operator=((const T *) value); - } const char *name; }; @@ -33,8 +27,9 @@ struct arg { /// Annotation for keyword arguments with default values template struct arg_t : public arg { constexpr arg_t(const char *name, const T &value, const char *descr = nullptr) - : arg(name), value(value), descr(descr) { } - T value; + : arg(name), value(&value), descr(descr) { } + + const T *value; const char *descr; }; @@ -246,7 +241,7 @@ struct process_attribute> : process_attribute_default> { /* Convert keyword value into a Python object */ object o = object(detail::type_caster::type>::cast( - a.value, return_value_policy::automatic, handle()), false); + *a.value, return_value_policy::automatic, handle()), false); if (!o) { #if !defined(NDEBUG) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index d25bc383..463f713b 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -17,6 +17,7 @@ NAMESPACE_BEGIN(pybind11) /* A few forward declarations */ class object; class str; class object; class dict; class iterator; +struct arg; template struct arg_t; namespace detail { class accessor; class args_proxy; class kwargs_proxy; } /// Holds a reference to a Python object (no reference counting) From c743e1b1b4c1ea4a0c5c1aee9802f02533633e2a Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Mon, 29 Aug 2016 03:05:42 +0200 Subject: [PATCH 02/10] Support keyword arguments and generalized unpacking in C++ A Python function can be called with the syntax: ```python foo(a1, a2, *args, ka=1, kb=2, **kwargs) ``` This commit adds support for the equivalent syntax in C++: ```c++ foo(a1, a2, *args, "ka"_a=1, "kb"_a=2, **kwargs) ``` In addition, generalized unpacking is implemented, as per PEP 448, which allows calls with multiple * and ** unpacking: ```python bar(*args1, 99, *args2, 101, **kwargs1, kz=200, **kwargs2) ``` and ```c++ bar(*args1, 99, *args2, 101, **kwargs1, "kz"_a=200, **kwargs2) ``` --- include/pybind11/attr.h | 8 -- include/pybind11/cast.h | 187 +++++++++++++++++++++++++++-- include/pybind11/common.h | 32 +++++ include/pybind11/pytypes.h | 13 +- tests/pybind11_tests.h | 1 + tests/test_callbacks.cpp | 62 +++++++++- tests/test_callbacks.py | 35 ++++++ tests/test_kwargs_and_defaults.cpp | 1 - 8 files changed, 321 insertions(+), 18 deletions(-) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index ba4dd841..638ebad7 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -296,9 +296,6 @@ template struct process_attribute struct process_attributes { static void init(const Args&... args, function_record *r) { @@ -319,11 +316,6 @@ template struct process_attributes { } }; -/// Compile-time integer sum -constexpr size_t constexpr_sum() { return 0; } -template -constexpr size_t constexpr_sum(T n, Ts... ns) { return n + constexpr_sum(ns...); } - /// Check the number of named arguments at compile time template ::value...), diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index cbb0ae5b..1101178e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -57,6 +57,7 @@ PYBIND11_NOINLINE inline internals &get_internals() { } catch (const index_error &e) { PyErr_SetString(PyExc_IndexError, e.what()); return; } catch (const key_error &e) { PyErr_SetString(PyExc_KeyError, e.what()); return; } catch (const value_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; + } catch (const type_error &e) { PyErr_SetString(PyExc_TypeError, e.what()); return; } catch (const stop_iteration &e) { PyErr_SetString(PyExc_StopIteration, e.what()); return; } catch (const std::bad_alloc &e) { PyErr_SetString(PyExc_MemoryError, e.what()); return; } catch (const std::domain_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; @@ -308,6 +309,7 @@ protected: }; template class type_caster : public type_caster_base { }; +template using make_caster = type_caster>; template class type_caster> : public type_caster_base { public: @@ -947,13 +949,184 @@ template object handle::operator()(Args&&... args) const { - tuple args_tuple = pybind11::make_tuple(std::forward(args)...); - object result(PyObject_CallObject(m_ptr, args_tuple.ptr()), false); - if (!result) - throw error_already_set(); - return result; +NAMESPACE_BEGIN(detail) +NAMESPACE_BEGIN(constexpr_impl) +/// Implementation details for constexpr functions +constexpr int first(int i) { return i; } +template +constexpr int first(int i, T v, Ts... vs) { return v ? i : first(i + 1, vs...); } + +constexpr int last(int /*i*/, int result) { return result; } +template +constexpr int last(int i, int result, T v, Ts... vs) { return last(i + 1, v ? i : result, vs...); } +NAMESPACE_END(constexpr_impl) + +/// Return the index of the first type in Ts which satisfies Predicate +template class Predicate, typename... Ts> +constexpr int constexpr_first() { return constexpr_impl::first(0, Predicate::value...); } + +/// Return the index of the last type in Ts which satisfies Predicate +template class Predicate, typename... Ts> +constexpr int constexpr_last() { return constexpr_impl::last(0, -1, Predicate::value...); } + +/// Helper class which collects only positional arguments for a Python function call. +/// A fancier version below can collect any argument, but this one is optimal for simple calls. +template +class simple_collector { +public: + template + simple_collector(Ts &&...values) + : m_args(pybind11::make_tuple(std::forward(values)...)) { } + + const tuple &args() const & { return m_args; } + dict kwargs() const { return {}; } + + tuple args() && { return std::move(m_args); } + + /// Call a Python function and pass the collected arguments + object call(PyObject *ptr) const { + auto result = object(PyObject_CallObject(ptr, m_args.ptr()), false); + if (!result) + throw error_already_set(); + return result; + } + +private: + tuple m_args; +}; + +/// Helper class which collects positional, keyword, * and ** arguments for a Python function call +template +class unpacking_collector { +public: + template + unpacking_collector(Ts &&...values) { + // Tuples aren't (easily) resizable so a list is needed for collection, + // but the actual function call strictly requires a tuple. + auto args_list = list(); + int _[] = { 0, (process(args_list, std::forward(values)), 0)... }; + ignore_unused(_); + + m_args = object(PyList_AsTuple(args_list.ptr()), false); + } + + const tuple &args() const & { return m_args; } + const dict &kwargs() const & { return m_kwargs; } + + tuple args() && { return std::move(m_args); } + dict kwargs() && { return std::move(m_kwargs); } + + /// Call a Python function and pass the collected arguments + object call(PyObject *ptr) const { + auto result = object(PyObject_Call(ptr, m_args.ptr(), m_kwargs.ptr()), false); + if (!result) + throw error_already_set(); + return result; + } + +private: + template + void process(list &args_list, T &&x) { + auto o = object(detail::make_caster::cast(std::forward(x), policy, nullptr), false); + if (!o) { +#if defined(NDEBUG) + argument_cast_error(); +#else + argument_cast_error(std::to_string(args_list.size()), type_id()); +#endif + } + args_list.append(o); + } + + void process(list &args_list, detail::args_proxy ap) { + for (const auto &a : ap) { + args_list.append(a.cast()); + } + } + + template + void process(list &/*args_list*/, arg_t &&a) { + if (m_kwargs[a.name]) { +#if defined(NDEBUG) + multiple_values_error(); +#else + multiple_values_error(a.name); +#endif + } + auto o = object(detail::make_caster::cast(*a.value, policy, nullptr), false); + if (!o) { +#if defined(NDEBUG) + argument_cast_error(); +#else + argument_cast_error(a.name, type_id()); +#endif + } + m_kwargs[a.name] = o; + } + + void process(list &/*args_list*/, detail::kwargs_proxy kp) { + for (const auto &k : dict(kp, true)) { + if (m_kwargs[k.first]) { +#if defined(NDEBUG) + multiple_values_error(); +#else + multiple_values_error(k.first.str()); +#endif + } + m_kwargs[k.first] = k.second; + } + } + + [[noreturn]] static void multiple_values_error() { + throw type_error("Got multiple values for keyword argument " + "(compile in debug mode for details)"); + } + + [[noreturn]] static void multiple_values_error(std::string name) { + throw type_error("Got multiple values for keyword argument '" + name + "'"); + } + + [[noreturn]] static void argument_cast_error() { + throw cast_error("Unable to convert call argument to Python object " + "(compile in debug mode for details)"); + } + + [[noreturn]] static void argument_cast_error(std::string name, std::string type) { + throw cast_error("Unable to convert call argument '" + name + + "' of type '" + type + "' to Python object"); + } + +private: + tuple m_args; + dict m_kwargs; +}; + +/// Collect only positional arguments for a Python function call +template ::value>> +simple_collector collect_arguments(Args &&...args) { + return {std::forward(args)...}; +} + +/// Collect all arguments, including keywords and unpacking (only instantiated when needed) +template ::value>> +unpacking_collector collect_arguments(Args &&...args) { + // Following argument order rules for generalized unpacking according to PEP 448 + static_assert( + constexpr_last() < constexpr_first() + && constexpr_last() < constexpr_first(), + "Invalid function call: positional args must precede keywords and ** unpacking; " + "* unpacking must precede ** unpacking" + ); + return {std::forward(args)...}; +} + +NAMESPACE_END(detail) + +template +object handle::operator()(Args &&...args) const { + return detail::collect_arguments(std::forward(args)...).call(m_ptr); } template struct intrinsic_type { typedef type template struct intrinsic_type { typedef typename intrinsic_type::type type; }; template struct intrinsic_type { typedef typename intrinsic_type::type type; }; template struct intrinsic_type { typedef typename intrinsic_type::type type; }; +template using intrinsic_t = typename intrinsic_type::type; /// Helper type to replace 'void' in some expressions struct void_type { }; +/// from __cpp_future__ import (convenient aliases from C++14/17) +template using bool_constant = std::integral_constant; +template using negation = bool_constant; +template using enable_if_t = typename std::enable_if::type; +template using conditional_t = typename std::conditional::type; + +/// Compile-time integer sum +constexpr size_t constexpr_sum() { return 0; } +template +constexpr size_t constexpr_sum(T n, Ts... ns) { return size_t{n} + constexpr_sum(ns...); } + +/// Return true if all/any Ts satify Predicate +#if !defined(_MSC_VER) +template class Predicate, typename... Ts> +using all_of_t = bool_constant<(constexpr_sum(Predicate::value...) == sizeof...(Ts))>; +template class Predicate, typename... Ts> +using any_of_t = bool_constant<(constexpr_sum(Predicate::value...) > 0)>; +#else +// MSVC workaround (2015 Update 3 has issues with some member type aliases and constexpr) +template class P, typename...> struct all_of_t : std::true_type { }; +template class P, typename T, typename... Ts> +struct all_of_t : conditional_t::value, all_of_t, std::false_type> { }; +template class P, typename...> struct any_of_t : std::false_type { }; +template class P, typename T, typename... Ts> +struct any_of_t : conditional_t::value, std::true_type, any_of_t> { }; +#endif + +/// Ignore that a variable is unused in compiler warnings +inline void ignore_unused(const int *) { } + NAMESPACE_END(detail) #define PYBIND11_RUNTIME_EXCEPTION(name) \ @@ -345,6 +376,7 @@ PYBIND11_RUNTIME_EXCEPTION(stop_iteration) PYBIND11_RUNTIME_EXCEPTION(index_error) PYBIND11_RUNTIME_EXCEPTION(key_error) PYBIND11_RUNTIME_EXCEPTION(value_error) +PYBIND11_RUNTIME_EXCEPTION(type_error) PYBIND11_RUNTIME_EXCEPTION(cast_error) /// Thrown when pybind11::cast or handle::call fail due to a type casting error PYBIND11_RUNTIME_EXCEPTION(reference_cast_error) /// Used internally diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 463f713b..5f60895f 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -16,7 +16,7 @@ NAMESPACE_BEGIN(pybind11) /* A few forward declarations */ -class object; class str; class object; class dict; class iterator; +class object; class str; class iterator; struct arg; template struct arg_t; namespace detail { class accessor; class args_proxy; class kwargs_proxy; } @@ -250,6 +250,17 @@ public: kwargs_proxy operator*() const { return kwargs_proxy(*this); } }; +/// Python argument categories (using PEP 448 terms) +template using is_keyword = std::is_base_of; +template using is_s_unpacking = std::is_same; // * unpacking +template using is_ds_unpacking = std::is_same; // ** unpacking +template using is_positional = bool_constant< + !is_keyword::value && !is_s_unpacking::value && !is_ds_unpacking::value +>; +template using is_keyword_or_ds = bool_constant< + is_keyword::value || is_ds_unpacking::value +>; + NAMESPACE_END(detail) #define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, CvtStmt) \ diff --git a/tests/pybind11_tests.h b/tests/pybind11_tests.h index 8af3154e..cf3cb36e 100644 --- a/tests/pybind11_tests.h +++ b/tests/pybind11_tests.h @@ -8,6 +8,7 @@ using std::cout; using std::endl; namespace py = pybind11; +using namespace pybind11::literals; class test_initializer { public: diff --git a/tests/test_callbacks.cpp b/tests/test_callbacks.cpp index 8e0a6cc7..bfb93269 100644 --- a/tests/test_callbacks.cpp +++ b/tests/test_callbacks.cpp @@ -71,6 +71,9 @@ struct Payload { } }; +/// Something to trigger a conversion error +struct Unregistered {}; + test_initializer callbacks([](py::module &m) { m.def("test_callback1", &test_callback1); m.def("test_callback2", &test_callback2); @@ -78,8 +81,65 @@ test_initializer callbacks([](py::module &m) { m.def("test_callback4", &test_callback4); m.def("test_callback5", &test_callback5); - /* Test cleanup of lambda closure */ + // Test keyword args and generalized unpacking + m.def("test_tuple_unpacking", [](py::function f) { + auto t1 = py::make_tuple(2, 3); + auto t2 = py::make_tuple(5, 6); + return f("positional", 1, *t1, 4, *t2); + }); + m.def("test_dict_unpacking", [](py::function f) { + auto d1 = py::dict(); + d1["key"] = py::cast("value"); + d1["a"] = py::cast(1); + auto d2 = py::dict(); + auto d3 = py::dict(); + d3["b"] = py::cast(2); + return f("positional", 1, **d1, **d2, **d3); + }); + + m.def("test_keyword_args", [](py::function f) { + return f("x"_a=10, "y"_a=20); + }); + + m.def("test_unpacking_and_keywords1", [](py::function f) { + auto args = py::make_tuple(2); + auto kwargs = py::dict(); + kwargs["d"] = py::cast(4); + return f(1, *args, "c"_a=3, **kwargs); + }); + + m.def("test_unpacking_and_keywords2", [](py::function f) { + auto kwargs1 = py::dict(); + kwargs1["a"] = py::cast(1); + auto kwargs2 = py::dict(); + kwargs2["c"] = py::cast(3); + kwargs2["d"] = py::cast(4); + return f("positional", *py::make_tuple(1), 2, *py::make_tuple(3, 4), 5, + "key"_a="value", **kwargs1, "b"_a=2, **kwargs2, "e"_a=5); + }); + + m.def("test_unpacking_error1", [](py::function f) { + auto kwargs = py::dict(); + kwargs["x"] = py::cast(3); + return f("x"_a=1, "y"_a=2, **kwargs); // duplicate ** after keyword + }); + + m.def("test_unpacking_error2", [](py::function f) { + auto kwargs = py::dict(); + kwargs["x"] = py::cast(3); + return f(**kwargs, "x"_a=1); // duplicate keyword after ** + }); + + m.def("test_arg_conversion_error1", [](py::function f) { + f(234, Unregistered(), "kw"_a=567); + }); + + m.def("test_arg_conversion_error2", [](py::function f) { + f(234, "expected_name"_a=Unregistered(), "kw"_a=567); + }); + + /* Test cleanup of lambda closure */ m.def("test_cleanup", []() -> std::function { Payload p; diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index d6e72f33..8f867d43 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -27,6 +27,41 @@ def test_callbacks(): assert f(number=43) == 44 +def test_keyword_args_and_generalized_unpacking(): + from pybind11_tests import (test_tuple_unpacking, test_dict_unpacking, test_keyword_args, + test_unpacking_and_keywords1, test_unpacking_and_keywords2, + test_unpacking_error1, test_unpacking_error2, + test_arg_conversion_error1, test_arg_conversion_error2) + + def f(*args, **kwargs): + return args, kwargs + + assert test_tuple_unpacking(f) == (("positional", 1, 2, 3, 4, 5, 6), {}) + assert test_dict_unpacking(f) == (("positional", 1), {"key": "value", "a": 1, "b": 2}) + assert test_keyword_args(f) == ((), {"x": 10, "y": 20}) + assert test_unpacking_and_keywords1(f) == ((1, 2), {"c": 3, "d": 4}) + assert test_unpacking_and_keywords2(f) == ( + ("positional", 1, 2, 3, 4, 5), + {"key": "value", "a": 1, "b": 2, "c": 3, "d": 4, "e": 5} + ) + + with pytest.raises(TypeError) as excinfo: + test_unpacking_error1(f) + assert "Got multiple values for keyword argument" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + test_unpacking_error2(f) + assert "Got multiple values for keyword argument" in str(excinfo.value) + + with pytest.raises(RuntimeError) as excinfo: + test_arg_conversion_error1(f) + assert "Unable to convert call argument" in str(excinfo.value) + + with pytest.raises(RuntimeError) as excinfo: + test_arg_conversion_error2(f) + assert "Unable to convert call argument" in str(excinfo.value) + + def test_lambda_closure_cleanup(): from pybind11_tests import test_cleanup, payload_cstats diff --git a/tests/test_kwargs_and_defaults.cpp b/tests/test_kwargs_and_defaults.cpp index bd244983..d35ca02a 100644 --- a/tests/test_kwargs_and_defaults.cpp +++ b/tests/test_kwargs_and_defaults.cpp @@ -56,7 +56,6 @@ test_initializer arg_keywords_and_defaults([](py::module &m) { m.def("args_function", &args_function); m.def("args_kwargs_function", &args_kwargs_function); - using namespace py::literals; m.def("kw_func_udl", &kw_func, "x"_a, "y"_a=300); m.def("kw_func_udl_z", &kw_func, "x"_a, "y"_a=0); From 67990d9e19421f63a08908a2f1ef65bced440d04 Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Mon, 29 Aug 2016 18:03:34 +0200 Subject: [PATCH 03/10] Add py::print() function Replicates Python API including keyword arguments. --- include/pybind11/pybind11.h | 27 +++++++++++++++++++++++++++ tests/conftest.py | 18 +++++++++++++----- tests/test_python_types.cpp | 14 ++++++++++++++ tests/test_python_types.py | 15 +++++++++++++++ 4 files changed, 69 insertions(+), 5 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 7bdf9137..2104671d 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1233,6 +1233,33 @@ public: } }; +NAMESPACE_BEGIN(detail) +PYBIND11_NOINLINE inline void print(tuple args, dict kwargs) { + auto strings = tuple(args.size()); + for (size_t i = 0; i < args.size(); ++i) { + strings[i] = args[i].cast().str(); + } + auto sep = kwargs["sep"] ? kwargs["sep"] : cast(" "); + auto line = sep.attr("join").cast()(strings); + + auto file = kwargs["file"] ? kwargs["file"].cast() + : module::import("sys").attr("stdout"); + auto write = file.attr("write").cast(); + write(line); + write(kwargs["end"] ? kwargs["end"] : cast("\n")); + + if (kwargs["flush"] && kwargs["flush"].cast()) { + file.attr("flush").cast()(); + } +} +NAMESPACE_END(detail) + +template +void print(Args &&...args) { + auto c = detail::collect_arguments(std::forward(args)...); + detail::print(c.args(), c.kwargs()); +} + #if defined(WITH_THREAD) /* The functions below essentially reproduce the PyGILState_* API using a RAII diff --git a/tests/conftest.py b/tests/conftest.py index 8ba0f488..eb6fd026 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -68,18 +68,22 @@ class Capture(object): def __init__(self, capfd): self.capfd = capfd self.out = "" + self.err = "" - def _flush_stdout(self): + def _flush(self): + """Workaround for issues on Windows: to be removed after tests get py::print""" sys.stdout.flush() - os.fsync(sys.stdout.fileno()) # make sure C++ output is also read - return self.capfd.readouterr()[0] + os.fsync(sys.stdout.fileno()) + sys.stderr.flush() + os.fsync(sys.stderr.fileno()) + return self.capfd.readouterr() def __enter__(self): - self._flush_stdout() + self._flush() return self def __exit__(self, *_): - self.out = self._flush_stdout() + self.out, self.err = self._flush() def __eq__(self, other): a = Output(self.out) @@ -100,6 +104,10 @@ class Capture(object): def unordered(self): return Unordered(self.out) + @property + def stderr(self): + return Output(self.err) + @pytest.fixture def capture(capfd): diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index 8ec7e26a..d7c06b2e 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -197,4 +197,18 @@ test_initializer python_types([](py::module &m) { .def_readwrite_static("value", &ExamplePythonTypes::value, "Static value member") .def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)") ; + + m.def("test_print_function", []() { + py::print("Hello, World!"); + py::print(1, 2.0, "three", true, std::string("-- multiple args")); + auto args = py::make_tuple("and", "a", "custom", "separator"); + py::print("*args", *args, "sep"_a="-"); + py::print("no new line here", "end"_a=" -- "); + py::print("next print"); + + auto py_stderr = py::module::import("sys").attr("stderr").cast(); + py::print("this goes to stderr", "file"_a=py_stderr); + + py::print("flush", "flush"_a=true); + }); }); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 3738d41c..4a25bfbf 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -218,3 +218,18 @@ def test_module(): assert ExamplePythonTypes.__module__ == "pybind11_tests" assert ExamplePythonTypes.get_set.__name__ == "get_set" assert ExamplePythonTypes.get_set.__module__ == "pybind11_tests" + + +def test_print(capture): + from pybind11_tests import test_print_function + + with capture: + test_print_function() + assert capture == """ + Hello, World! + 1 2.0 three True -- multiple args + *args-and-a-custom-separator + no new line here -- next print + flush + """ + assert capture.stderr == "this goes to stderr" From 66aa2728f44be58e9786c685dfbf166978579f3d Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Mon, 29 Aug 2016 18:04:30 +0200 Subject: [PATCH 04/10] Add py::str::format() method --- include/pybind11/pytypes.h | 10 ++++++++++ tests/test_python_types.cpp | 8 ++++++++ tests/test_python_types.py | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 5f60895f..374f5420 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -385,8 +385,18 @@ public: pybind11_fail("Unable to extract string contents! (invalid type)"); return std::string(buffer, (size_t) length); } + + template + str format(Args &&...args) const { + return attr("format").cast()(std::forward(args)...); + } }; +inline namespace literals { +/// String literal version of str +inline str operator"" _s(const char *s, size_t size) { return {s, size}; } +} + inline pybind11::str handle::str() const { PyObject *strValue = PyObject_Str(m_ptr); #if PY_MAJOR_VERSION < 3 diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index d7c06b2e..1b462aba 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -210,5 +210,13 @@ test_initializer python_types([](py::module &m) { py::print("this goes to stderr", "file"_a=py_stderr); py::print("flush", "flush"_a=true); + + py::print("{a} + {b} = {c}"_s.format("a"_a="py::print", "b"_a="str.format", "c"_a="this")); + }); + + m.def("test_str_format", []() { + auto s1 = "{} + {} = {}"_s.format(1, 2, 3); + auto s2 = "{a} + {b} = {c}"_s.format("a"_a=1, "b"_a=2, "c"_a=3); + return py::make_tuple(s1, s2); }); }); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 4a25bfbf..36960697 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -231,5 +231,14 @@ def test_print(capture): *args-and-a-custom-separator no new line here -- next print flush + py::print + str.format = this """ assert capture.stderr == "this goes to stderr" + + +def test_str_api(): + from pybind11_tests import test_str_format + + s1, s2 = test_str_format() + assert s1 == "1 + 2 = 3" + assert s1 == s2 From 15a112f8ff8a55660d682ba178cb59184e766f48 Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Tue, 30 Aug 2016 12:05:53 +0200 Subject: [PATCH 05/10] Add py::dict() keyword constructor --- include/pybind11/cast.h | 4 ++++ include/pybind11/pytypes.h | 3 +++ tests/test_callbacks.cpp | 23 +++++++---------------- tests/test_python_types.cpp | 6 ++++++ tests/test_python_types.py | 6 ++++++ 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 1101178e..3c39841f 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1148,6 +1148,10 @@ inline object handle::operator()(detail::args_proxy args, detail::kwargs_proxy k return result; } +template +dict::dict(Args &&...args) + : dict(detail::unpacking_collector<>(std::forward(args)...).kwargs()) { } + #define PYBIND11_MAKE_OPAQUE(Type) \ namespace pybind11 { namespace detail { \ template<> class type_caster : public type_caster_base { }; \ diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 374f5420..e25f1a79 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -589,6 +589,9 @@ public: dict() : object(PyDict_New(), false) { if (!m_ptr) pybind11_fail("Could not allocate dict object!"); } + template ::value>> + dict(Args &&...args); size_t size() const { return (size_t) PyDict_Size(m_ptr); } detail::dict_iterator begin() const { return (++detail::dict_iterator(*this, 0)); } detail::dict_iterator end() const { return detail::dict_iterator(); } diff --git a/tests/test_callbacks.cpp b/tests/test_callbacks.cpp index bfb93269..31d4e39a 100644 --- a/tests/test_callbacks.cpp +++ b/tests/test_callbacks.cpp @@ -89,12 +89,9 @@ test_initializer callbacks([](py::module &m) { }); m.def("test_dict_unpacking", [](py::function f) { - auto d1 = py::dict(); - d1["key"] = py::cast("value"); - d1["a"] = py::cast(1); + auto d1 = py::dict("key"_a="value", "a"_a=1); auto d2 = py::dict(); - auto d3 = py::dict(); - d3["b"] = py::cast(2); + auto d3 = py::dict("b"_a=2); return f("positional", 1, **d1, **d2, **d3); }); @@ -104,30 +101,24 @@ test_initializer callbacks([](py::module &m) { m.def("test_unpacking_and_keywords1", [](py::function f) { auto args = py::make_tuple(2); - auto kwargs = py::dict(); - kwargs["d"] = py::cast(4); + auto kwargs = py::dict("d"_a=4); return f(1, *args, "c"_a=3, **kwargs); }); m.def("test_unpacking_and_keywords2", [](py::function f) { - auto kwargs1 = py::dict(); - kwargs1["a"] = py::cast(1); - auto kwargs2 = py::dict(); - kwargs2["c"] = py::cast(3); - kwargs2["d"] = py::cast(4); + auto kwargs1 = py::dict("a"_a=1); + auto kwargs2 = py::dict("c"_a=3, "d"_a=4); return f("positional", *py::make_tuple(1), 2, *py::make_tuple(3, 4), 5, "key"_a="value", **kwargs1, "b"_a=2, **kwargs2, "e"_a=5); }); m.def("test_unpacking_error1", [](py::function f) { - auto kwargs = py::dict(); - kwargs["x"] = py::cast(3); + auto kwargs = py::dict("x"_a=3); return f("x"_a=1, "y"_a=2, **kwargs); // duplicate ** after keyword }); m.def("test_unpacking_error2", [](py::function f) { - auto kwargs = py::dict(); - kwargs["x"] = py::cast(3); + auto kwargs = py::dict("x"_a=3); return f(**kwargs, "x"_a=1); // duplicate keyword after ** }); diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index 1b462aba..e527c0a0 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -219,4 +219,10 @@ test_initializer python_types([](py::module &m) { auto s2 = "{a} + {b} = {c}"_s.format("a"_a=1, "b"_a=2, "c"_a=3); return py::make_tuple(s1, s2); }); + + m.def("test_dict_keyword_constructor", []() { + auto d1 = py::dict("x"_a=1, "y"_a=2); + auto d2 = py::dict("z"_a=3, **d1); + return d2; + }); }); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 36960697..087a9a20 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -242,3 +242,9 @@ def test_str_api(): s1, s2 = test_str_format() assert s1 == "1 + 2 = 3" assert s1 == s2 + + +def test_dict_api(): + from pybind11_tests import test_dict_keyword_constructor + + assert test_dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3} From 625bd48a91551e413153cb9c6abe6a266c7ce26d Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Fri, 2 Sep 2016 16:40:49 +0200 Subject: [PATCH 06/10] Document calling function with keyword arguments from C++ --- docs/advanced.rst | 70 ++++++++++++++++++++++++++++++++++++++++------ docs/changelog.rst | 7 +++++ 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index fdbf6cd2..cf588af3 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -1622,24 +1622,76 @@ It is also possible to call python functions via ``operator()``. 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. +Keyword arguments are also supported. In Python, there is the usual call syntax: + +.. code-block:: python + + def f(number, say, to): + ... # function code + + f(1234, say="hello", to=some_instance) # keyword call in Python + +In C++, the same call can be made using: .. code-block:: cpp - py::function f = <...>; + using pybind11::literals; // to bring in the `_a` literal + f(1234, "say"_a="hello", "to"_a=some_instance); // keyword call in C++ + +Unpacking of ``*args`` and ``**kwargs`` is also possible and can be mixed with +other arguments: + +.. code-block:: cpp + + // * unpacking + py::tuple args = py::make_tuple(1234, "hello", some_instance); + f(*args); + + // ** unpacking + py::dict kwargs = py::dict("number"_a=1234, "say"_a="hello", "to"_a=some_instance); + f(**kwargs); + + // mixed keywords, * and ** unpacking py::tuple args = py::make_tuple(1234); - py::dict kwargs; - kwargs["y"] = py::cast(5678); - py::object result = f(*args, **kwargs); + py::dict kwargs = py::dict("to"_a=some_instance); + f(*args, "say"_a="hello", **kwargs); + +Generalized unpacking according to PEP448_ is also supported: + +.. code-block:: cpp + + py::dict kwargs1 = py::dict("number"_a=1234); + py::dict kwargs2 = py::dict("to"_a=some_instance); + f(**kwargs1, "say"_a="hello", **kwargs2); .. seealso:: The file :file:`tests/test_python_types.cpp` contains a complete example that demonstrates passing native Python types in more detail. The - file :file:`tests/test_kwargs_and_defaults.cpp` discusses usage - of ``args`` and ``kwargs``. + file :file:`tests/test_callbacks.cpp` presents a few examples of calling + Python functions from C++, including keywords arguments and unpacking. + +.. _PEP448: https://www.python.org/dev/peps/pep-0448/ + +Using Python's print function in C++ +==================================== + +The usual way to write output in C++ is using ``std::cout`` while in Python one +would use ``print``. Since these methods use different buffers, mixing them can +lead to output order issues. To resolve this, pybind11 modules can use the +:func:`py::print` function which writes to Python's ``sys.stdout`` for consistency. + +Python's ``print`` function is replicated in the C++ API including optional +keyword arguments ``sep``, ``end``, ``file``, ``flush``. Everything works as +expected in Python: + +.. code-block:: cpp + + py::print(1, 2.0, "three"); // 1 2.0 three + py::print(1, 2.0, "three", "sep"_a="-"); // 1-2.0-three + + auto args = py::make_tuple("unpacked", true); + py::print("->", *args, "end"_a="<-"); // -> unpacked True <- Default arguments revisited =========================== diff --git a/docs/changelog.rst b/docs/changelog.rst index 0485e925..a9886e03 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -46,6 +46,13 @@ Breaking changes queued for v2.0.0 (Not yet released) * Added constructors for ``str`` and ``bytes`` from zero-terminated char pointers, and from char pointers and length. * Added ``memoryview`` wrapper type which is constructible from ``buffer_info``. +* New syntax to call a Python function from C++ using keyword arguments and unpacking, + e.g. ``foo(1, 2, "z"_a=3)`` or ``bar(1, *args, "z"_a=3, **kwargs)``. +* Added ``py::print()`` function which replicates Python's API and writes to Python's + ``sys.stdout`` by default (as opposed to C's ``stdout`` like ``std::cout``). +* Added ``py::dict`` keyword constructor:``auto d = dict("number"_a=42, "name"_a="World");`` +* Added ``py::str::format()`` method and ``_s`` literal: + ``py::str s = "1 + 2 = {}"_s.format(3);`` * Various minor improvements of library internals (no user-visible changes) 1.8.1 (July 12, 2016) From 16db1bfbd70dd67b5cbe6ffe03f876dfae1c69ff Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Sat, 3 Sep 2016 17:25:40 +0200 Subject: [PATCH 07/10] Remove superseded handle::operator() overloads The variadic handle::operator() offers the same functionality as well as mixed positional, keyword, * and ** arguments. The tests are also superseded by the ones in `test_callbacks`. --- include/pybind11/cast.h | 14 -------------- include/pybind11/pytypes.h | 2 -- tests/test_kwargs_and_defaults.cpp | 9 --------- tests/test_kwargs_and_defaults.py | 7 ++----- 4 files changed, 2 insertions(+), 30 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 3c39841f..de185c27 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1134,20 +1134,6 @@ template (std::forward(args)...); } -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_proxy args, detail::kwargs_proxy kwargs) const { - object result(PyObject_Call(m_ptr, args.ptr(), kwargs.ptr()), false); - if (!result) - throw error_already_set(); - return result; -} - template dict::dict(Args &&...args) : dict(detail::unpacking_collector<>(std::forward(args)...).kwargs()) { } diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index e25f1a79..da22bbd3 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -48,8 +48,6 @@ public: object call(Args&&... args) const; template object operator()(Args&&... args) 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; } diff --git a/tests/test_kwargs_and_defaults.cpp b/tests/test_kwargs_and_defaults.cpp index d35ca02a..24fc0cd5 100644 --- a/tests/test_kwargs_and_defaults.cpp +++ b/tests/test_kwargs_and_defaults.cpp @@ -20,13 +20,6 @@ std::string kw_func4(const std::vector &entries) { return ret; } -py::object call_kw_func(py::function f) { - py::tuple args = py::make_tuple(1234); - py::dict kwargs; - kwargs["y"] = py::cast(5678); - return f(*args, **kwargs); -} - py::tuple args_function(py::args args) { return args; } @@ -49,9 +42,7 @@ test_initializer arg_keywords_and_defaults([](py::module &m) { std::vector list; list.push_back(13); list.push_back(17); - 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/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index 14d9c5ab..0e1ea805 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -1,7 +1,6 @@ import pytest -from pybind11_tests import (kw_func0, kw_func1, kw_func2, kw_func3, kw_func4, call_kw_func, - args_function, args_kwargs_function, kw_func_udl, kw_func_udl_z, - KWClass) +from pybind11_tests import (kw_func0, kw_func1, kw_func2, kw_func3, kw_func4, args_function, + args_kwargs_function, kw_func_udl, kw_func_udl_z, KWClass) def test_function_signatures(doc): @@ -49,8 +48,6 @@ def test_named_arguments(msg): def test_arg_and_kwargs(): - assert call_kw_func(kw_func2) == "x=1234, y=5678" - args = 'arg1_value', 'arg2_value', 3 assert args_function(*args) == args From 56e86ed094024eacf50aaabcfb0baa3bb182585f Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Sun, 4 Sep 2016 21:30:08 +0200 Subject: [PATCH 08/10] Workaround for py::dict() constructor on MSVC MSVC fails to compile if the constructor is defined out-of-line. The error states that it cannot deduce the type of the default template parameter which is used for SFINAE. --- include/pybind11/cast.h | 4 ---- include/pybind11/common.h | 4 ++++ include/pybind11/pytypes.h | 13 +++++++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index de185c27..144d2dc4 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1134,10 +1134,6 @@ template (std::forward(args)...); } -template -dict::dict(Args &&...args) - : dict(detail::unpacking_collector<>(std::forward(args)...).kwargs()) { } - #define PYBIND11_MAKE_OPAQUE(Type) \ namespace pybind11 { namespace detail { \ template<> class type_caster : public type_caster_base { }; \ diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 88f09019..3d5aeb22 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -358,6 +358,10 @@ template class P, typename T, typename... Ts> struct any_of_t : conditional_t::value, std::true_type, any_of_t> { }; #endif +/// Defer the evaluation of type T until types Us are instantiated +template struct deferred_type { using type = T; }; +template using deferred_t = typename deferred_type::type; + /// Ignore that a variable is unused in compiler warnings inline void ignore_unused(const int *) { } diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index da22bbd3..d7fda31d 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -259,6 +259,12 @@ template using is_keyword_or_ds = bool_constant< is_keyword::value || is_ds_unpacking::value >; +// Call argument collector forward declarations +template +class simple_collector; +template +class unpacking_collector; + NAMESPACE_END(detail) #define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, CvtStmt) \ @@ -588,8 +594,11 @@ public: if (!m_ptr) pybind11_fail("Could not allocate dict object!"); } template ::value>> - dict(Args &&...args); + typename = detail::enable_if_t::value>, + // MSVC workaround: it can't compile an out-of-line definition, so defer the collector + typename collector = detail::deferred_t, Args...>> + dict(Args &&...args) : dict(collector(std::forward(args)...).kwargs()) { } + size_t size() const { return (size_t) PyDict_Size(m_ptr); } detail::dict_iterator begin() const { return (++detail::dict_iterator(*this, 0)); } detail::dict_iterator end() const { return detail::dict_iterator(); } From 8fe13b889672ca1db697a9c178349373e55d4014 Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Mon, 5 Sep 2016 14:30:56 +0200 Subject: [PATCH 09/10] Apply make_caster and intrinsic_t aliases everywhere --- include/pybind11/attr.h | 2 +- include/pybind11/cast.h | 38 +++++++++++++++++++------------------- include/pybind11/stl.h | 18 +++++++++--------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 638ebad7..6c413284 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -240,7 +240,7 @@ struct process_attribute> : process_attribute_default> { r->args.emplace_back("self", nullptr, handle()); /* Convert keyword value into a Python object */ - object o = object(detail::type_caster::type>::cast( + auto o = object(detail::make_caster::cast( *a.value, return_value_policy::automatic, handle()), false); if (!o) { diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 144d2dc4..213fbd2e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -252,8 +252,8 @@ protected: /* Determine suitable casting operator */ template using cast_op_type = typename std::conditional::type>::value, - typename std::add_pointer::type>::type, - typename std::add_lvalue_reference::type>::type>::type; + typename std::add_pointer>::type, + typename std::add_lvalue_reference>::type>::type; /// Generic type caster for objects stored on the heap template class type_caster_base : public type_caster_generic { @@ -612,8 +612,8 @@ public: } static handle cast(const type &src, return_value_policy policy, handle parent) { - object o1 = object(type_caster::type>::cast(src.first, policy, parent), false); - object o2 = object(type_caster::type>::cast(src.second, policy, parent), false); + object o1 = object(make_caster::cast(src.first, policy, parent), false); + object o2 = object(make_caster::cast(src.second, policy, parent), false); if (!o1 || !o2) return handle(); tuple result(2); @@ -624,24 +624,24 @@ public: static PYBIND11_DESCR name() { return type_descr( - _("Tuple[") + type_caster::type>::name() + - _(", ") + type_caster::type>::name() + _("]")); + _("Tuple[") + make_caster::name() + _(", ") + make_caster::name() + _("]") + ); } template using cast_op_type = type; operator type() { - return type(first .operator typename type_caster::type>::template cast_op_type(), - second.operator typename type_caster::type>::template cast_op_type()); + return type(first.operator typename make_caster::template cast_op_type(), + second.operator typename make_caster::template cast_op_type()); } protected: - type_caster::type> first; - type_caster::type> second; + make_caster first; + make_caster second; }; template class type_caster> { typedef std::tuple type; - typedef std::tuple::type...> itype; + typedef std::tuple...> itype; typedef std::tuple args_type; typedef std::tuple args_kwargs_type; public: @@ -681,7 +681,7 @@ public: } static PYBIND11_DESCR element_names() { - return detail::concat(type_caster::type>::name()...); + return detail::concat(make_caster::name()...); } static PYBIND11_DESCR name() { @@ -706,12 +706,12 @@ public: protected: template ReturnValue call(Func &&f, index_sequence) { return f(std::get(value) - .operator typename type_caster::type>::template cast_op_type()...); + .operator typename make_caster::template cast_op_type()...); } template type cast(index_sequence) { return type(std::get(value) - .operator typename type_caster::type>::template cast_op_type()...); + .operator typename make_caster::template cast_op_type()...); } template bool load(handle src, bool convert, index_sequence) { @@ -728,7 +728,7 @@ protected: /* Implementation: Convert a C++ tuple into a Python tuple */ template static handle cast(const type &src, return_value_policy policy, handle parent, index_sequence) { std::array entries {{ - object(type_caster::type>::cast(std::get(src), policy, parent), false)... + object(make_caster::cast(std::get(src), policy, parent), false)... }}; for (const auto &entry: entries) if (!entry) @@ -741,7 +741,7 @@ protected: } protected: - std::tuple::type>...> value; + std::tuple...> value; }; /// Type caster for holder types like std::shared_ptr, etc. @@ -848,7 +848,7 @@ template using move_never = std::integral_constant T cast(const handle &handle) { - typedef detail::type_caster::type> type_caster; + using type_caster = detail::make_caster; type_caster conv; if (!conv.load(handle, true)) { #if defined(NDEBUG) @@ -868,7 +868,7 @@ template object cast(const T &value, policy = std::is_pointer::value ? return_value_policy::take_ownership : return_value_policy::copy; else if (policy == return_value_policy::automatic_reference) policy = std::is_pointer::value ? return_value_policy::reference : return_value_policy::copy; - return object(detail::type_caster::type>::cast(value, policy, parent), false); + return object(detail::make_caster::cast(value, policy, parent), false); } template T handle::cast() const { return pybind11::cast(*this); } @@ -929,7 +929,7 @@ template tuple make_tuple(Args&&... args_) { const size_t size = sizeof...(Args); std::array args { - { object(detail::type_caster::type>::cast( + { object(detail::make_caster::cast( std::forward(args_), policy, nullptr), false)... } }; for (auto &arg_value : args) { diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 2c47841b..4390efac 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -26,8 +26,8 @@ NAMESPACE_BEGIN(pybind11) NAMESPACE_BEGIN(detail) template struct set_caster { - typedef Type type; - typedef type_caster::type> key_conv; + using type = Type; + using key_conv = make_caster; bool load(handle src, bool convert) { pybind11::set s(src, true); @@ -57,9 +57,9 @@ template struct set_caster { }; template struct map_caster { - typedef Type type; - typedef type_caster::type> key_conv; - typedef type_caster::type> value_conv; + using type = Type; + using key_conv = make_caster; + using value_conv = make_caster; bool load(handle src, bool convert) { dict d(src, true); @@ -93,8 +93,8 @@ template struct map_caster { }; template struct list_caster { - typedef Type type; - typedef type_caster::type> value_conv; + using type = Type; + using value_conv = make_caster; bool load(handle src, bool convert) { list l(src, true); @@ -138,8 +138,8 @@ template struct type_caster, Type> { }; template struct type_caster> { - typedef std::array array_type; - typedef type_caster::type> value_conv; + using array_type = std::array; + using value_conv = make_caster; bool load(handle src, bool convert) { list l(src, true); From 60b26802fd78273b2bef33315cc3bbbd12539afd Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Tue, 6 Sep 2016 00:49:21 +0200 Subject: [PATCH 10/10] Make keyword argument hold a py::object instead of T* With this change arg_t is no longer a template, but it must remain so for backward compatibility. Thus, a non-template arg_v is introduced, while a dummy template alias arg_t is there to keep old code from breaking. This can be remove in the next major release. The implementation of arg_v also needed to be placed a little earlier in the headers because it's not a template any more and unpacking_collector needs more than a forward declaration. --- include/pybind11/attr.h | 41 ++++---------------------------- include/pybind11/cast.h | 48 +++++++++++++++++++++++++++++++++----- include/pybind11/pytypes.h | 2 +- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 6c413284..9acb3e3a 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -14,30 +14,6 @@ NAMESPACE_BEGIN(pybind11) -/// Annotation for keyword arguments -struct arg { - constexpr explicit arg(const char *name) : name(name) { } - - template - constexpr arg_t operator=(const T &value) const { return {name, value}; } - - const char *name; -}; - -/// Annotation for keyword arguments with default values -template struct arg_t : public arg { - constexpr arg_t(const char *name, const T &value, const char *descr = nullptr) - : arg(name), value(&value), descr(descr) { } - - const T *value; - const char *descr; -}; - -inline namespace literals { -/// String literal version of arg -constexpr arg operator"" _a(const char *name, size_t) { return arg(name); } -} - /// Annotation for methods struct is_method { handle class_; is_method(const handle &c) : class_(c) { } }; @@ -233,21 +209,14 @@ template <> struct process_attribute : process_attribute_default { }; /// Process a keyword argument attribute (*with* a default value) -template -struct process_attribute> : process_attribute_default> { - static void init(const arg_t &a, function_record *r) { +template <> struct process_attribute : process_attribute_default { + static void init(const arg_v &a, function_record *r) { if (r->class_ && r->args.empty()) r->args.emplace_back("self", nullptr, handle()); - /* Convert keyword value into a Python object */ - auto o = object(detail::make_caster::cast( - *a.value, return_value_policy::automatic, handle()), false); - - if (!o) { + if (!a.value) { #if !defined(NDEBUG) - std::string descr(typeid(T).name()); - detail::clean_type_id(descr); - descr = "'" + std::string(a.name) + ": " + descr + "'"; + auto descr = "'" + std::string(a.name) + ": " + a.type + "'"; if (r->class_) { if (r->name) descr += " in method '" + (std::string) r->class_.str() + "." + (std::string) r->name + "'"; @@ -264,7 +233,7 @@ struct process_attribute> : process_attribute_default> { "Compile in debug mode for more information."); #endif } - r->args.emplace_back(a.name, a.descr, o.release()); + r->args.emplace_back(a.name, a.descr, a.value.inc_ref()); } }; diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 213fbd2e..c8c8f77b 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -949,6 +949,44 @@ template arg_v operator=(T &&value) const; + + const char *name; +}; + +/// Annotation for keyword arguments with values +struct arg_v : arg { + template + arg_v(const char *name, T &&x, const char *descr = nullptr) + : arg(name), + value(detail::make_caster::cast(x, return_value_policy::automatic, handle()), false), + descr(descr) +#if !defined(NDEBUG) + , type(type_id()) +#endif + { } + + object value; + const char *descr; +#if !defined(NDEBUG) + std::string type; +#endif +}; + +template +arg_v arg::operator=(T &&value) const { return {name, std::forward(value)}; } + +/// Alias for backward compatibility -- to be remove in version 2.0 +template using arg_t = arg_v; + +inline namespace literals { +/// String literal version of arg +constexpr arg operator"" _a(const char *name, size_t) { return arg(name); } +} + NAMESPACE_BEGIN(detail) NAMESPACE_BEGIN(constexpr_impl) /// Implementation details for constexpr functions @@ -1044,8 +1082,7 @@ private: } } - template - void process(list &/*args_list*/, arg_t &&a) { + void process(list &/*args_list*/, arg_v a) { if (m_kwargs[a.name]) { #if defined(NDEBUG) multiple_values_error(); @@ -1053,15 +1090,14 @@ private: multiple_values_error(a.name); #endif } - auto o = object(detail::make_caster::cast(*a.value, policy, nullptr), false); - if (!o) { + if (!a.value) { #if defined(NDEBUG) argument_cast_error(); #else - argument_cast_error(a.name, type_id()); + argument_cast_error(a.name, a.type); #endif } - m_kwargs[a.name] = o; + m_kwargs[a.name] = a.value; } void process(list &/*args_list*/, detail::kwargs_proxy kp) { diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index d7fda31d..51f16291 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -17,7 +17,7 @@ NAMESPACE_BEGIN(pybind11) /* A few forward declarations */ class object; class str; class iterator; -struct arg; template struct arg_t; +struct arg; struct arg_v; namespace detail { class accessor; class args_proxy; class kwargs_proxy; } /// Holds a reference to a Python object (no reference counting)