Skip to content

Commit f748ed7

Browse files
committed
Final cleanups for iostream, call guard usage
Squashed commit of the following: commit 4cdbdd3809cae488107c33838eb4dd2346de73f8 Author: Henry Fredrick Schreiner <[email protected]> Date: Tue Aug 22 10:12:23 2017 -0700 Linting fix commit 4ff64ce2ebf9b26c60575f8edb93b488f4677431 Author: Henry Fredrick Schreiner <[email protected]> Date: Tue Aug 22 08:34:38 2017 -0700 Docs cleanup, changelog update commit 00cc177c2e1148b71000e9564f8fcf4947f0b8f5 Author: Henry Fredrick Schreiner <[email protected]> Date: Tue Aug 22 08:21:35 2017 -0700 Adding scoped_estream_redirect, for call_guard and simplification commit 30899eaeba30ff453338040c93a221879351efb7 Author: Henry Fredrick Schreiner <[email protected]> Date: Tue Aug 22 07:33:47 2017 -0700 Docs cleanup and update commit fe1a63a125330a01684ed8a26d77523195c90735 Author: Henry Fredrick Schreiner <[email protected]> Date: Mon Aug 21 17:36:17 2017 -0700 Fix linter issues commit e2b8f8ad6abcac4864f78c93f8b89e85215aa0a0 Author: Henry Fredrick Schreiner <[email protected]> Date: Mon Aug 21 16:47:44 2017 -0700 Dropping some extra unneeded thises and unused default commit 2e8bec13e94dcba71693788200e4ed7c40e2ac60 Author: Henry Fredrick Schreiner <[email protected]> Date: Mon Aug 21 16:43:41 2017 -0700 Sync on destuction, and add tests to check flush behavior commit 314d4514685a3f17a13da5864c1f055d66688458 Author: Henry Fredrick Schreiner <[email protected]> Date: Mon Aug 21 16:11:52 2017 -0700 Testing and documenting simple call with call_guard
1 parent a65b11e commit f748ed7

File tree

5 files changed

+135
-51
lines changed

5 files changed

+135
-51
lines changed

docs/advanced/pycpp/utilities.rst

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ expected in Python:
2626
Capturing standard output from ostream
2727
======================================
2828

29-
Often, a library may use the streams ``std::cout`` and ``std::cerr`` to print, but this does
30-
not play well with Python's standard ``sys.stdout`` and ``sys.stderr`` redirection.
31-
This can be fixed using a guard around the library function that has output:
29+
Often, a library will use the streams :cpp:var:`std::cout` and
30+
:cpp:var:`std::cerr` to print, but this does not play well with Python's
31+
standard :py:obj:`sys.stdout` and :py:obj:`sys.stderr` redirection. Replacing a
32+
library's printing with :cpp:func:`py::print` may not be feasible. This can be
33+
fixed using a guard around the library function that redirects output to the
34+
corresponding python streams:
3235

3336
.. code-block:: cpp
3437
@@ -37,38 +40,54 @@ This can be fixed using a guard around the library function that has output:
3740
3841
...
3942
40-
// Add a scoped redirect for your noisy function
43+
// Add a scoped redirect for your noisy code
4144
m.def("noisy_func", []() {
4245
py::scoped_ostream_redirect stream(
4346
std::cout, // std::ostream&
44-
py::module::import("sys").attr("stdout") // Python descriptor to output to
47+
py::module::import("sys").attr("stdout") // Python output
4548
);
4649
call_noisy_func();
4750
});
4851
49-
This method respects flushes on the output streams. This allows the output to be redirected
50-
in realtime, such as to a Jupyter notebook. The two arguments, the C++ stream and the Python
51-
output, are optional, and default to standard output if not given.
52+
This method respects flushes on the output streams, and will flush if needed
53+
when the scoped guard is destroyed. This allows the output to be redirected in
54+
real time, such as to a Jupyter notebook. The two arguments, the C++ stream and
55+
the Python output, are optional, and default to standard output if not given. An
56+
extra type, `py::scoped_estream_redirect`, is identical execpt for
57+
defaulting to the error stream; this can be useful with `py::call_guard`, which
58+
allows multiple items, but uses the default constuctor:
5259

53-
The redirection can also be done in python with the addition of a context manager, using the `py::add_ostream_redirect()` function:
60+
.. code-block:: py
61+
62+
// Alternative: Call single function using call guard
63+
m.def("noisy_func", &call_noisy_function,
64+
py::call_guard<py::scoped_ostream_redirect,
65+
py::scoped_estream_redirect>());
66+
67+
The redirection can also be done in python with the addition of a context
68+
manager, using the `py::add_ostream_redirect()` function:
5469

5570
.. code-block:: cpp
5671
5772
py::add_ostream_redirect(m, "ostream_redirect");
5873
59-
The name in Python defaults to ``ostream_redirect`` if no name is passed. This creates the following context manager in Python:
74+
The name in Python defaults to ``ostream_redirect`` if no name is passed. This
75+
creates the following context manager in Python:
6076

6177
.. code-block:: python
6278
6379
with ostream_redirect(stdout=True, stderr=True):
6480
noisy_function()
6581
66-
This defaults redirecting ``stdout`` if neither keyword argument is given.
82+
The added context manager defaults to redirecting both streams, though you can
83+
use the keyword arguments to disable one of the streams if needed.
6784

6885
.. note:
6986
70-
The above methods will not redirect direct output to file descriptors, such as ``fprintf``. For those cases, you'll need to
71-
redirect the file descriptors either directly in C or with Python's `os.dup2` function in an operating-system dependent way.
87+
The above methods will not redirect direct output to file descriptors, such
88+
as ``fprintf``. For those cases, you'll need to redirect the file
89+
descriptors either directly in C or with Python's :py:obj:`os.dup2` function
90+
in an operating-system dependent way.
7291
7392
.. _eval:
7493

docs/changelog.rst

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ Starting with version 1.8.0, pybind11 releases use a `semantic versioning
99
v2.2.0 (Not yet released)
1010
-----------------------------------------------------
1111

12-
<<<<<<< HEAD
1312
* Support for embedding the Python interpreter. See the
1413
:doc:`documentation page </advanced/embedding>` for a
1514
full overview of the new features.
@@ -128,8 +127,8 @@ v2.2.0 (Not yet released)
128127
See :ref:`call_policies` for details.
129128
`#740 <https://github.com/pybind/pybind11/pull/740>`_.
130129

131-
* Utility for redirecting `std::cout` and `std::cerr` added,
132-
`py::scoped_stl_redirect`, RAII in C++;
130+
* Utility for redirecting C++ streams like `std::cout`,
131+
`py::scoped_ostream_redirect`, RAII in C++;
133132
and a context manager in python. See :ref:`ostream_redirect` or
134133
`#1005 <https://github.com/pybind/pybind11/pull/1009>`_.
135134

@@ -290,7 +289,6 @@ v2.2.0 (Not yet released)
290289
`#923 <https://github.com/pybind/pybind11/pull/923>`_,
291290
`#963 <https://github.com/pybind/pybind11/pull/963>`_.
292291

293-
294292
v2.1.1 (April 7, 2017)
295293
-----------------------------------------------------
296294

include/pybind11/iostream.h

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,33 @@ class pythonbuf : public std::streambuf {
3131

3232
int overflow(int c) {
3333
if (!traits_type::eq_int_type(c, traits_type::eof())) {
34-
*this->pptr() = traits_type::to_char_type(c);
35-
this->pbump(1);
34+
*pptr() = traits_type::to_char_type(c);
35+
pbump(1);
3636
}
37-
return this->sync() ? traits_type::not_eof(c) : traits_type::eof();
37+
return sync() ? traits_type::not_eof(c) : traits_type::eof();
3838
}
3939

4040
int sync() {
41-
if (this->pbase() != this->pptr()) {
41+
if (pbase() != pptr()) {
4242
// This subtraction cannot be negative, so dropping the sign
43-
str line(this->pbase(), static_cast<size_t>(this->pptr() - this->pbase()));
43+
str line(pbase(), static_cast<size_t>(pptr() - pbase()));
4444

4545
pywrite(line);
4646
pyflush();
4747

48-
this->setp(this->pbase(), this->epptr());
48+
setp(pbase(), epptr());
4949
}
5050
return 0;
5151
}
5252
public:
5353
pythonbuf(object pyostream)
5454
: pywrite(pyostream.attr("write")),
5555
pyflush(pyostream.attr("flush")) {
56-
this->setp(this->d_buffer, this->d_buffer + sizeof(this->d_buffer) - 1);
56+
setp(d_buffer, d_buffer + sizeof(d_buffer) - 1);
57+
}
58+
/// Sync before destroy
59+
~pythonbuf() {
60+
sync();
5761
}
5862
};
5963

@@ -83,21 +87,22 @@ NAMESPACE_END(detail)
8387
\endrst */
8488

8589
class scoped_ostream_redirect {
86-
std::streambuf * old {nullptr};
90+
protected:
91+
std::streambuf * old;
8792
std::ostream& costream;
8893
detail::pythonbuf buffer;
8994

9095
public:
96+
9197
scoped_ostream_redirect(
9298
std::ostream& costream = std::cout,
93-
object pyostream = module::import("sys").attr("stdout") )
99+
object pyostream = module::import("sys").attr("stdout"))
94100
: costream(costream), buffer(pyostream) {
95101
old = costream.rdbuf(&buffer);
96102
}
97103

98104
~scoped_ostream_redirect() {
99105
costream.rdbuf(old);
100-
101106
}
102107

103108
scoped_ostream_redirect(const scoped_ostream_redirect &) = delete;
@@ -106,39 +111,52 @@ class scoped_ostream_redirect {
106111
scoped_ostream_redirect &operator=(scoped_ostream_redirect &&) = delete;
107112
};
108113

114+
115+
/** \rst
116+
Scoped ostream output redirect with cerr defaults
117+
118+
This class is provided primary to make call_guards easier to make.
119+
120+
.. code_block:: cpp
121+
122+
m.def("noisy_func", &noisy_func,
123+
py::call_guard<scoped_ostream_redirect,
124+
scoped_estream_redirect>());
125+
126+
\endrst */
127+
class scoped_estream_redirect : public scoped_ostream_redirect {
128+
public:
129+
scoped_estream_redirect(
130+
std::ostream& costream = std::cerr,
131+
object pyostream = module::import("sys").attr("stderr"))
132+
: scoped_ostream_redirect(costream,pyostream) {}
133+
134+
scoped_estream_redirect(const scoped_estream_redirect &) = delete;
135+
scoped_estream_redirect(scoped_estream_redirect &&other) = default;
136+
scoped_estream_redirect &operator=(const scoped_estream_redirect &) = delete;
137+
scoped_estream_redirect &operator=(scoped_estream_redirect &&) = delete;
138+
};
139+
140+
109141
NAMESPACE_BEGIN(detail)
110142

111143
// Class to redirect output as a context manager. C++ backend.
112144
class OstreamRedirect {
113145
bool do_stdout_;
114146
bool do_stderr_;
115147
std::unique_ptr<scoped_ostream_redirect> redirect_stdout;
116-
std::unique_ptr<scoped_ostream_redirect> redirect_stderr;
148+
std::unique_ptr<scoped_estream_redirect> redirect_stderr;
117149

118150
public:
119151

120-
OstreamRedirect(bool do_stdout = false, bool do_stderr = false)
152+
OstreamRedirect(bool do_stdout = true, bool do_stderr = true)
121153
: do_stdout_(do_stdout), do_stderr_(do_stderr) {}
122154

123155
void enter() {
124-
// If stdout is true, or if both are false
125-
if (do_stdout_ || (!do_stdout_ && !do_stderr_)) {
126-
redirect_stdout.reset(
127-
new scoped_ostream_redirect(
128-
std::cout,
129-
module::import("sys").attr("stdout")
130-
)
131-
);
132-
}
133-
134-
if (do_stderr_) {
135-
redirect_stderr.reset(
136-
new scoped_ostream_redirect(
137-
std::cerr,
138-
module::import("sys").attr("stderr")
139-
)
140-
);
141-
}
156+
if (do_stdout_)
157+
redirect_stdout.reset(new scoped_ostream_redirect());
158+
if (do_stderr_)
159+
redirect_stderr.reset(new scoped_estream_redirect());
142160
}
143161

144162
void exit() {
@@ -177,9 +195,9 @@ NAMESPACE_END(detail)
177195
178196
\endrst */
179197

180-
class_<detail::OstreamRedirect> add_ostream_redirect(module m, std::string name = "ostream_redirect") {
198+
inline class_<detail::OstreamRedirect> add_ostream_redirect(module m, std::string name = "ostream_redirect") {
181199
return class_<detail::OstreamRedirect>(m, name.c_str())
182-
.def(init<bool,bool>(), arg("stdout")=false, arg("stderr")=false)
200+
.def(init<bool,bool>(), arg("stdout")=true, arg("stderr")=true)
183201
.def("__enter__", [](detail::OstreamRedirect &self) {
184202
self.enter();
185203
})

tests/test_iostream.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@
1313
#include <iostream>
1414

1515

16+
void noisy_function(std::string msg, bool flush) {
17+
18+
std::cout << msg;
19+
if (flush)
20+
std::cout << std::flush;
21+
}
22+
23+
void noisy_funct_dual(std::string msg, std::string emsg) {
24+
std::cout << msg;
25+
std::cerr << emsg;
26+
}
27+
1628
TEST_SUBMODULE(iostream, m) {
1729

1830
add_ostream_redirect(m);
@@ -29,11 +41,21 @@ TEST_SUBMODULE(iostream, m) {
2941
std::cout << msg << std::flush;
3042
});
3143

44+
m.def("guard_output", &noisy_function,
45+
py::call_guard<py::scoped_ostream_redirect>(),
46+
py::arg("msg"), py::arg("flush")=true);
47+
3248
m.def("captured_err", [](std::string msg) {
3349
py::scoped_ostream_redirect redir(std::cerr, py::module::import("sys").attr("stderr"));
3450
std::cerr << msg << std::flush;
3551
});
3652

53+
m.def("noisy_function", &noisy_function, py::arg("msg"), py::arg("flush") = true);
54+
55+
m.def("dual_guard", &noisy_funct_dual,
56+
py::call_guard<py::scoped_ostream_redirect, py::scoped_estream_redirect>(),
57+
py::arg("msg"), py::arg("emsg"));
58+
3759
m.def("raw_output", [](std::string msg) {
3860
std::cout << msg << std::flush;
3961
});

tests/test_iostream.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,40 @@ def test_captured(capsys):
5454
assert stderr == msg
5555

5656

57+
def test_guard_capture(capsys):
58+
msg = "I've been redirected to Python, I hope!"
59+
m.guard_output(msg)
60+
stdout, stderr = capsys.readouterr()
61+
assert stdout == msg
62+
assert stderr == ''
63+
64+
5765
def test_series_captured(capture):
5866
with capture:
5967
m.captured_output("a")
6068
m.captured_output("b")
6169
assert capture == "ab"
6270

6371

72+
def test_flush(capfd):
73+
msg = "(not flushed)"
74+
msg2 = "(flushed)"
75+
76+
with m.ostream_redirect():
77+
m.noisy_function(msg, flush=False)
78+
stdout, stderr = capfd.readouterr()
79+
assert stdout == ''
80+
81+
m.noisy_function(msg2, flush=True)
82+
stdout, stderr = capfd.readouterr()
83+
assert stdout == msg + msg2
84+
85+
m.noisy_function(msg, flush=False)
86+
87+
stdout, stderr = capfd.readouterr()
88+
assert stdout == msg
89+
90+
6491
def test_not_captured(capfd):
6592
msg = "Something that should not show up in log"
6693
stream = StringIO()
@@ -149,7 +176,7 @@ def test_redirect_err(capfd):
149176

150177
stream = StringIO()
151178
with redirect_stderr(stream):
152-
with m.ostream_redirect(stderr=True):
179+
with m.ostream_redirect(stdout=False):
153180
m.raw_output(msg)
154181
m.raw_err(msg2)
155182
stdout, stderr = capfd.readouterr()
@@ -166,7 +193,7 @@ def test_redirect_both(capfd):
166193
stream2 = StringIO()
167194
with redirect_stdout(stream):
168195
with redirect_stderr(stream2):
169-
with m.ostream_redirect(stdout=True, stderr=True):
196+
with m.ostream_redirect():
170197
m.raw_output(msg)
171198
m.raw_err(msg2)
172199
stdout, stderr = capfd.readouterr()

0 commit comments

Comments
 (0)