diff --git a/docs/advanced.rst b/docs/advanced.rst index fdbf6cd2de..cf588af3e2 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 0485e925a1..a9886e0393 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) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 7925b8e295..9acb3e3aa5 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -14,35 +14,6 @@ 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; -}; - -/// 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; - 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) { } }; @@ -238,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 */ - object o = object(detail::type_caster::type>::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 + "'"; @@ -269,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()); } }; @@ -301,9 +265,6 @@ template struct process_attribute struct process_attributes { static void init(const Args&... args, function_record *r) { @@ -324,11 +285,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 cbb0ae5b87..c8c8f77ba1 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; @@ -251,8 +252,8 @@ class type_caster_generic { /* 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 { @@ -308,6 +309,7 @@ template class type_caster_base : public type_caster_generic { }; template class type_caster : public type_caster_base { }; +template using make_caster = type_caster>; template class type_caster> : public type_caster_base { public: @@ -610,8 +612,8 @@ template class type_caster> { } 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); @@ -622,24 +624,24 @@ template class type_caster> { 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: @@ -679,7 +681,7 @@ template class type_caster> { } static PYBIND11_DESCR element_names() { - return detail::concat(type_caster::type>::name()...); + return detail::concat(make_caster::name()...); } static PYBIND11_DESCR name() { @@ -704,12 +706,12 @@ template class type_caster> { 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) { @@ -726,7 +728,7 @@ template class type_caster> { /* 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) @@ -739,7 +741,7 @@ template class type_caster> { } protected: - std::tuple::type>...> value; + std::tuple...> value; }; /// Type caster for holder types like std::shared_ptr, etc. @@ -846,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) @@ -866,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); } @@ -927,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) { @@ -947,32 +949,225 @@ 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; +/// Annotation for keyword arguments +struct arg { + constexpr explicit arg(const char *name) : name(name) { } + 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); } } -template object handle::call(Args &&... args) const { - return operator()(std::forward(args)...); +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()); + } + } + + void process(list &/*args_list*/, arg_v a) { + if (m_kwargs[a.name]) { +#if defined(NDEBUG) + multiple_values_error(); +#else + multiple_values_error(a.name); +#endif + } + if (!a.value) { +#if defined(NDEBUG) + argument_cast_error(); +#else + argument_cast_error(a.name, a.type); +#endif + } + m_kwargs[a.name] = a.value; + } + + 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)...}; } -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; +/// 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)...}; } -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; +NAMESPACE_END(detail) + +template +object handle::operator()(Args &&...args) const { + return detail::collect_arguments(std::forward(args)...).call(m_ptr); +} + +template object handle::call(Args &&... args) const { + return operator()(std::forward(args)...); } #define PYBIND11_MAKE_OPAQUE(Type) \ diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 762a1e09e2..3d5aeb22aa 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -326,10 +326,45 @@ 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 + +/// 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 *) { } + NAMESPACE_END(detail) #define PYBIND11_RUNTIME_EXCEPTION(name) \ @@ -345,6 +380,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/pybind11.h b/include/pybind11/pybind11.h index 7bdf91372d..2104671dbe 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1233,6 +1233,33 @@ class exception : public object { } }; +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/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index d25bc3836a..51f162918d 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -16,7 +16,8 @@ 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; struct arg_v; namespace detail { class accessor; class args_proxy; class kwargs_proxy; } /// Holds a reference to a Python object (no reference counting) @@ -47,8 +48,6 @@ class handle { 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; } @@ -249,6 +248,23 @@ class args_proxy : public handle { 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 +>; + +// Call argument collector forward declarations +template +class simple_collector; +template +class unpacking_collector; + NAMESPACE_END(detail) #define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, CvtStmt) \ @@ -373,8 +389,18 @@ class str : public object { 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 @@ -567,6 +593,12 @@ class dict : public object { dict() : object(PyDict_New(), false) { if (!m_ptr) pybind11_fail("Could not allocate dict object!"); } + template ::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(); } diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 2c47841b54..4390efac9b 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); diff --git a/tests/conftest.py b/tests/conftest.py index 8ba0f4880e..eb6fd02600 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 @@ def __contains__(self, item): def unordered(self): return Unordered(self.out) + @property + def stderr(self): + return Output(self.err) + @pytest.fixture def capture(capfd): diff --git a/tests/pybind11_tests.h b/tests/pybind11_tests.h index 8af3154ef6..cf3cb36ef7 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 8e0a6cc7b0..31d4e39aa6 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,56 @@ 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("key"_a="value", "a"_a=1); + auto d2 = py::dict(); + auto d3 = py::dict("b"_a=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("d"_a=4); + return f(1, *args, "c"_a=3, **kwargs); + }); + + m.def("test_unpacking_and_keywords2", [](py::function f) { + 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("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("x"_a=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 d6e72f333d..8f867d43a4 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -27,6 +27,41 @@ def func3(a): 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 bd244983f5..24fc0cd5b6 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,14 +42,11 @@ 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); - 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); diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index 14d9c5ab89..0e1ea805ee 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 diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index 8ec7e26a27..e527c0a0d6 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -197,4 +197,32 @@ 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); + + 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); + }); + + 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 3738d41c8c..087a9a20df 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -218,3 +218,33 @@ 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 + 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 + + +def test_dict_api(): + from pybind11_tests import test_dict_keyword_constructor + + assert test_dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3}