Commit a3dbdc67 by Wenzel Jakob Committed by GitHub

Merge pull request #372 from dean0x7d/keywords

Keyword arguments and generalized unpacking for C++ API
parents 6f017cf6 60b26802
......@@ -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<MyClass>();
The special ``f(*args)`` and ``f(*args, **kwargs)`` syntax is also supported to
supply arbitrary argument and keyword lists, although these cannot be mixed
with other parameters.
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
===========================
......
......@@ -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)
......
......@@ -14,35 +14,6 @@
NAMESPACE_BEGIN(pybind11)
template <typename T> struct arg_t;
/// Annotation for keyword arguments
struct arg {
constexpr explicit arg(const char *name) : name(name) { }
template <typename T>
constexpr arg_t<T> operator=(const T &value) const { return {name, value}; }
template <typename T, size_t N>
constexpr arg_t<const T *> operator=(T const (&value)[N]) const {
return operator=((const T *) value);
}
const char *name;
};
/// Annotation for keyword arguments with default values
template <typename T> 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<arg> : process_attribute_default<arg> {
};
/// Process a keyword argument attribute (*with* a default value)
template <typename T>
struct process_attribute<arg_t<T>> : process_attribute_default<arg_t<T>> {
static void init(const arg_t<T> &a, function_record *r) {
template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
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<typename detail::intrinsic_type<T>::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<arg_t<T>> : process_attribute_default<arg_t<T>> {
"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 <int Nurse, int Patient> struct process_attribute<keep_alive<Nurse, Pat
static void postcall(handle args, handle ret) { keep_alive_impl(Nurse, Patient, args, ret); }
};
/// Ignore that a variable is unused in compiler warnings
inline void ignore_unused(const int *) { }
/// Recursively iterate over variadic template arguments
template <typename... Args> struct process_attributes {
static void init(const Args&... args, function_record *r) {
......@@ -324,11 +285,6 @@ template <typename... Args> struct process_attributes {
}
};
/// Compile-time integer sum
constexpr size_t constexpr_sum() { return 0; }
template <typename T, typename... Ts>
constexpr size_t constexpr_sum(T n, Ts... ns) { return n + constexpr_sum(ns...); }
/// Check the number of named arguments at compile time
template <typename... Extra,
size_t named = constexpr_sum(std::is_base_of<arg, Extra>::value...),
......
......@@ -326,10 +326,45 @@ template <typename T> struct intrinsic_type<T&> { typedef type
template <typename T> struct intrinsic_type<T&&> { typedef typename intrinsic_type<T>::type type; };
template <typename T, size_t N> struct intrinsic_type<const T[N]> { typedef typename intrinsic_type<T>::type type; };
template <typename T, size_t N> struct intrinsic_type<T[N]> { typedef typename intrinsic_type<T>::type type; };
template <typename T> using intrinsic_t = typename intrinsic_type<T>::type;
/// Helper type to replace 'void' in some expressions
struct void_type { };
/// from __cpp_future__ import (convenient aliases from C++14/17)
template <bool B> using bool_constant = std::integral_constant<bool, B>;
template <class T> using negation = bool_constant<!T::value>;
template <bool B, typename T = void> using enable_if_t = typename std::enable_if<B, T>::type;
template <bool B, typename T, typename F> using conditional_t = typename std::conditional<B, T, F>::type;
/// Compile-time integer sum
constexpr size_t constexpr_sum() { return 0; }
template <typename T, typename... Ts>
constexpr size_t constexpr_sum(T n, Ts... ns) { return size_t{n} + constexpr_sum(ns...); }
/// Return true if all/any Ts satify Predicate<T>
#if !defined(_MSC_VER)
template <template<typename> class Predicate, typename... Ts>
using all_of_t = bool_constant<(constexpr_sum(Predicate<Ts>::value...) == sizeof...(Ts))>;
template <template<typename> class Predicate, typename... Ts>
using any_of_t = bool_constant<(constexpr_sum(Predicate<Ts>::value...) > 0)>;
#else
// MSVC workaround (2015 Update 3 has issues with some member type aliases and constexpr)
template <template<typename> class P, typename...> struct all_of_t : std::true_type { };
template <template<typename> class P, typename T, typename... Ts>
struct all_of_t<P, T, Ts...> : conditional_t<P<T>::value, all_of_t<P, Ts...>, std::false_type> { };
template <template<typename> class P, typename...> struct any_of_t : std::false_type { };
template <template<typename> class P, typename T, typename... Ts>
struct any_of_t<P, T, Ts...> : conditional_t<P<T>::value, std::true_type, any_of_t<P, Ts...>> { };
#endif
/// Defer the evaluation of type T until types Us are instantiated
template <typename T, typename... /*Us*/> struct deferred_type { using type = T; };
template <typename T, typename... Us> using deferred_t = typename deferred_type<T, Us...>::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
......
......@@ -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<object>().str();
}
auto sep = kwargs["sep"] ? kwargs["sep"] : cast(" ");
auto line = sep.attr("join").cast<object>()(strings);
auto file = kwargs["file"] ? kwargs["file"].cast<object>()
: module::import("sys").attr("stdout");
auto write = file.attr("write").cast<object>();
write(line);
write(kwargs["end"] ? kwargs["end"] : cast("\n"));
if (kwargs["flush"] && kwargs["flush"].cast<bool>()) {
file.attr("flush").cast<object>()();
}
}
NAMESPACE_END(detail)
template <return_value_policy policy = return_value_policy::automatic_reference, typename... Args>
void print(Args &&...args) {
auto c = detail::collect_arguments<policy>(std::forward<Args>(args)...);
detail::print(c.args(), c.kwargs());
}
#if defined(WITH_THREAD)
/* The functions below essentially reproduce the PyGILState_* API using a RAII
......
......@@ -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 @@ public:
object call(Args&&... args) const;
template <return_value_policy policy = return_value_policy::automatic_reference, typename ... Args>
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 @@ public:
kwargs_proxy operator*() const { return kwargs_proxy(*this); }
};
/// Python argument categories (using PEP 448 terms)
template <typename T> using is_keyword = std::is_base_of<arg, T>;
template <typename T> using is_s_unpacking = std::is_same<args_proxy, T>; // * unpacking
template <typename T> using is_ds_unpacking = std::is_same<kwargs_proxy, T>; // ** unpacking
template <typename T> using is_positional = bool_constant<
!is_keyword<T>::value && !is_s_unpacking<T>::value && !is_ds_unpacking<T>::value
>;
template <typename T> using is_keyword_or_ds = bool_constant<
is_keyword<T>::value || is_ds_unpacking<T>::value
>;
// Call argument collector forward declarations
template <return_value_policy policy = return_value_policy::automatic_reference>
class simple_collector;
template <return_value_policy policy = return_value_policy::automatic_reference>
class unpacking_collector;
NAMESPACE_END(detail)
#define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, CvtStmt) \
......@@ -373,8 +389,18 @@ public:
pybind11_fail("Unable to extract string contents! (invalid type)");
return std::string(buffer, (size_t) length);
}
template <typename... Args>
str format(Args &&...args) const {
return attr("format").cast<object>()(std::forward<Args>(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 @@ public:
dict() : object(PyDict_New(), false) {
if (!m_ptr) pybind11_fail("Could not allocate dict object!");
}
template <typename... Args,
typename = detail::enable_if_t<detail::all_of_t<detail::is_keyword_or_ds, Args...>::value>,
// MSVC workaround: it can't compile an out-of-line definition, so defer the collector
typename collector = detail::deferred_t<detail::unpacking_collector<>, Args...>>
dict(Args &&...args) : dict(collector(std::forward<Args>(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(); }
......
......@@ -26,8 +26,8 @@ NAMESPACE_BEGIN(pybind11)
NAMESPACE_BEGIN(detail)
template <typename Type, typename Key> struct set_caster {
typedef Type type;
typedef type_caster<typename intrinsic_type<Key>::type> key_conv;
using type = Type;
using key_conv = make_caster<Key>;
bool load(handle src, bool convert) {
pybind11::set s(src, true);
......@@ -57,9 +57,9 @@ template <typename Type, typename Key> struct set_caster {
};
template <typename Type, typename Key, typename Value> struct map_caster {
typedef Type type;
typedef type_caster<typename intrinsic_type<Key>::type> key_conv;
typedef type_caster<typename intrinsic_type<Value>::type> value_conv;
using type = Type;
using key_conv = make_caster<Key>;
using value_conv = make_caster<Value>;
bool load(handle src, bool convert) {
dict d(src, true);
......@@ -93,8 +93,8 @@ template <typename Type, typename Key, typename Value> struct map_caster {
};
template <typename Type, typename Value> struct list_caster {
typedef Type type;
typedef type_caster<typename intrinsic_type<Value>::type> value_conv;
using type = Type;
using value_conv = make_caster<Value>;
bool load(handle src, bool convert) {
list l(src, true);
......@@ -138,8 +138,8 @@ template <typename Type, typename Alloc> struct type_caster<std::list<Type, Allo
: list_caster<std::list<Type, Alloc>, Type> { };
template <typename Type, size_t Size> struct type_caster<std::array<Type, Size>> {
typedef std::array<Type, Size> array_type;
typedef type_caster<typename intrinsic_type<Type>::type> value_conv;
using array_type = std::array<Type, Size>;
using value_conv = make_caster<Type>;
bool load(handle src, bool convert) {
list l(src, true);
......
......@@ -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):
......
......@@ -8,6 +8,7 @@ using std::cout;
using std::endl;
namespace py = pybind11;
using namespace pybind11::literals;
class test_initializer {
public:
......
......@@ -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<void(void)> {
Payload p;
......
......@@ -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
......
......@@ -20,13 +20,6 @@ std::string kw_func4(const std::vector<int> &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<int> 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);
......
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
......
......@@ -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::object>();
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;
});
});
......@@ -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}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment