Add py::print() function

Replicates Python API including keyword arguments.
This commit is contained in:
Dean Moldovan 2016-08-29 18:03:34 +02:00
parent c743e1b1b4
commit 67990d9e19
4 changed files with 69 additions and 5 deletions

View File

@ -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

View File

@ -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):

View File

@ -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::object>();
py::print("this goes to stderr", "file"_a=py_stderr);
py::print("flush", "flush"_a=true);
});
});

View File

@ -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"