From 67990d9e19421f63a08908a2f1ef65bced440d04 Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Mon, 29 Aug 2016 18:03:34 +0200 Subject: [PATCH] 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 7bdf91372..2104671db 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 8ba0f4880..eb6fd0260 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 8ec7e26a2..d7c06b2ea 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 3738d41c8..4a25bfbf5 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"