-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Stdout redirection #1009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stdout redirection #1009
Conversation
Odd, you don't seem to be able to |
My subjective 2c: this is somewhat half-baked since it doesn't work with C-level output. In fact, this is possible to implement completely outside of |
As long as you are working with C++, this works, and it does the redirection directly in the C++ code. Since libraries usually don't change IO, this either works if This could be extended eventually to provide a FD version; it would need a series of platform checks, but that code (even on Python) has a series of checks as well. Like the Python version, it would not be fully standard, relying on non-standard extensions like |
Just my 2c as well, and I'm biased. 😉 Also, is ipybind listed somewhere officially? It seems like a very useful tool! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The C-level redirects were pretty flakey when we were using them in pytest (before py::print
). The platform-specific implementations also make them complicated.
The C++ specific solution in this PR seems pretty nice to me: simple and portable. Looks good overall, but I left some comments which should be addressed.
Some other things:
-
I'm not sure if this should be a new header or just added toNever mind, as @jagerman points out below,pybind11/stl.h
. Does anyone else have any opinions on this?pybind11/stl.h
does already include and define a custom streamoperator<<
forpy::object
which is kind of related.stl.h
is for type casters. -
Maybe add a note in the docs that only
std
streams are redirected, but not C output functions. -
Some of the output capture is leaking into the test log. Ideally, that shouldn't be there.
docs/advanced/pycpp/utilities.rst
Outdated
@@ -23,6 +23,39 @@ expected in Python: | |||
|
|||
.. _eval: | |||
|
|||
Capturing ``std::cout`` and ``std::cerr`` usage | |||
=============================================== |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The .. _eval:
tag has ended up in the wrong place. It should point to the section below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
include/pybind11/iostream.h
Outdated
|
||
class pythonbuf : public std::streambuf { | ||
private: | ||
typedef std::streambuf::traits_type traits_type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typedef
-> using
.
include/pybind11/iostream.h
Outdated
|
||
int sync() { | ||
if (this->pbase() != this->pptr()) { | ||
std::string line(this->pbase(), this->pptr()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The write()
call is going to do an automatic std::string
-> py::str
conversion. But you could also construct py::str
directly using its (char *, size_t)
constructor.
include/pybind11/iostream.h
Outdated
|
||
auto write = pyostream.attr("write"); | ||
write(line); | ||
pyostream.attr("flush")(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider making write
and flush
member variables instead of pyostream
.
include/pybind11/iostream.h
Outdated
} | ||
}; | ||
|
||
class opythonstream : private virtual pythonbuf, public std::ostream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't pythonbuf
just be a member variable? To avoid multiple inheritance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not trivially, since it will be constructed in the wrong order, as the final object is required to construct the class it would be a member of. Let me know if you have any ideas other than manually building objects first and then passing around smart pointers or similar.
tests/CMakeLists.txt
Outdated
@@ -40,6 +40,8 @@ set(PYBIND11_TEST_FILES | |||
test_eval.cpp | |||
test_exceptions.cpp | |||
test_factory_constructors.cpp | |||
test_globaliostream.cpp | |||
test_iostream.cpp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regardless of the header question above, I think it would be best to merge these into test_stl.cpp
since there's not a lot of new stream tests. Adding complete new translation units really bloats the compile time of the test suite.
Also, merge the individual test functions into test_globaliostream()
and test_iostream()
when moved into test_stl.py
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merged globaliostream and iostream, can easily merge iostream to stl, for example, let me know if it's still a good idea, and if the py file should be merged too. I like having separate files, since there's not much shared between them, but can change it easily.
tests/test_globaliostream.cpp
Outdated
TEST_SUBMODULE(globaliostream, m) { | ||
// test_evals | ||
m.def("redirect_output", []() { | ||
return py::capsule(new py::scoped_output_redirect( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that this is going for an RAII-style guard, but those should really be implemented using context managers in Python. Something like the following (not actually tested):
struct RedirectOutput {
std::unique_ptr<py:: scoped_output_redirect> redirect;
}
py::class_<RedirectOutput>(m, "redirect_output")
.def("__enter__", [](RedirectOutput &self) {
self.redirect.reset(new py::scoped_output_redirect(...))
})
.def("__exit__", [](RedirectOutput &self, py::args) {
self.redirect.reset();
});
Note: it's convention for Python context managers to have lowercase names.
with m.redirect_output():
# redirected here
with capture:
# can be nested
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redesigned, and made available in the iostream.h header. I've tried to tweak the names a bit too to be clearer.
Fixed the easier comments, will work on the rest of it tomorrow. Thanks for the review! I could put it in the detail folder then include in |
Remaining tasks:
|
I vote, at least weakly, in favour of a new header. |
Ah, indeed. And with the proposed header reorg from #708 it would become |
That's most of the changes, plus a bit of renaming/redesign based on the comments. The remaining questions: multiple inheritance doesn't seem clean to avoid, due to construction order. The class is otherwise very simple, doing almost nothing past the multiple inheritance part. The second question is if the test files should be combined. I like keeping the python files separate, but I can combine the C++ files (I have a local change stashed that I can push up to do that if that's still agreed upon). The code in the files is mostly orthogonal. |
Would something like the following work? class opythonstream : public std::ostream {
public:
opythonstream(object pyostream) : std::ostream(new pythonbuf(pyostream)) { }
virtual ~opythonstream() { delete rdbuf(nullptr); }
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good to me. The separate test file is fine. Just one more thing: It should be possible to make a buffer
simplification -- see the comment below.
@@ -0,0 +1,188 @@ | |||
#pragma once |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a license notice like in test_iostream.cpp
and the other headers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied from there.
include/pybind11/iostream.h
Outdated
std::ostream& costream = std::cout, | ||
object pyostream = module::import("sys").attr("stdout") ) | ||
: costream(costream), buffer(pyostream) { | ||
old = costream.rdbuf(buffer.rdbuf()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the opythonstream
class isn't even needed. The buffer could just be:
detail::pythonbuf buffer;
And then:
old = costream.rdbuf(&buffer);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes it simpler and cleaner! Done.
Oh, cool, I just realized that this can be combined with call guard: m.def("noisy_func", &noisy_func, py::call_guard<py::scoped_ostream_redirect>()); |
You are right, I'm adding that to the tests and docs 👍 |
Added tests to verify flushing worked, and auto-flush if pythonbuf is destroyed (a problem revealed by the added tests). I dropped a bit of unneeded verbosity, as well. |
I think this is ready, finished polishing it off. Primarily due to the usefulness of m.def("noisy_function", &noisy_function,
py::call_guard<py::scoped_ostream_redirect,
py::scoped_estream_redirect>()); This also simplified some other code without losing generality. Other changes are mostly cosmetic; Docs have been nicely formatted and expanded a bit, etc. |
Can you squash the commits down to one or two commits with appropriate merged/updated commit messages, and rebase against current master (rather than merging the current master branch)? (That will keep the |
Rebased, squashed down to 5 commits that each had a valid, working version. (I can merge to fewer if needed, or squash-and-merge should work now). |
Finally the Mac build finished. |
Missed an inline (the usage of |
@henryiii Our sphinx setup is a bit peculiar so I went over and fixed up some reference links and added a section/links to the API docs. Minor (whitespace) style changes in the header. Only significant bits:
Let me know if anything looks off here. If not, I think this PR can be merged. |
Squashed commit of the following: commit 3f55f7c Author: Henry Fredrick Schreiner <[email protected]> Date: Fri Aug 18 09:35:47 2017 -0400 Fix short underline in docs commit bc8ac7d Author: Henry Fredrick Schreiner <[email protected]> Date: Fri Aug 18 08:53:15 2017 -0400 Typo fix in test commit e53f073 Author: Henry Fredrick Schreiner <[email protected]> Date: Fri Aug 18 08:43:12 2017 -0400 Linting fixes commit d8f47fb Author: Henry Fredrick Schreiner <[email protected]> Date: Fri Aug 18 00:13:43 2017 -0400 Linting fixes commit 84fe118 Author: Henry Fredrick Schreiner <[email protected]> Date: Fri Aug 18 00:11:25 2017 -0400 Docs typo fixed from lintig commit 3187674 Author: Henry Fredrick Schreiner <[email protected]> Date: Fri Aug 18 00:03:11 2017 -0400 Fixing Pypy tests commit 6a97ecc Author: Henry Fredrick Schreiner <[email protected]> Date: Thu Aug 17 23:26:16 2017 -0400 Minor cleanup from style lint commit c1c14c9 Author: Henry Fredrick Schreiner <[email protected]> Date: Thu Aug 17 22:56:35 2017 -0400 Changelog update commit 57d1d88 Author: Henry Fredrick Schreiner <[email protected]> Date: Thu Aug 17 22:53:37 2017 -0400 Adding tests for psudo-global iostream wrapping commit bcb6cc8 Author: Henry Fredrick Schreiner <[email protected]> Date: Thu Aug 17 22:34:08 2017 -0400 Adding docs and triple-test commit 54993c9 Author: Henry Fredrick Schreiner <[email protected]> Date: Thu Aug 17 22:21:08 2017 -0400 Adding method suggested in pybind#1005
Squashed commit of the following: commit 94d65fb040bf10eea9cda3093e73d87ec2c503e9 Author: Henry Fredrick Schreiner <[email protected]> Date: Sun Aug 20 23:52:38 2017 -0700 Fixing refer error commit 3ba58dc110d3f313e9176f5f17830e903f8f41c8 Author: Henry Fredrick Schreiner <[email protected]> Date: Sun Aug 20 23:15:51 2017 -0700 Fix typo in windows fix commit 0100145a240187f8d97b348daada162f1808de5f Author: Henry Fredrick Schreiner <[email protected]> Date: Sun Aug 20 23:06:37 2017 -0700 Fix header not linking in docs commit 2a8273df216143fbb42e104b46d129b00146cda2 Author: Henry Fredrick Schreiner <[email protected]> Date: Sun Aug 20 23:04:10 2017 -0700 Fixing usage of reserved word on windows commit caefb6c8d8bf3f362dd871b6e94343a226f2585a Author: Henry Fredrick Schreiner <[email protected]> Date: Sun Aug 20 22:37:34 2017 -0700 Removing output in logs, better testing commit 315645e3883b8ee801af9ccedf6d6cdf0f092a60 Author: Henry Fredrick Schreiner <[email protected]> Date: Sun Aug 20 21:43:05 2017 -0700 Renames and cleanup commit 3841c79a3914ffb32bf0b6192c9d06acbe2e8d57 Author: Henry Fredrick Schreiner <[email protected]> Date: Sun Aug 20 14:43:52 2017 -0700 Typo fix commit 861478aae97ee09bfd9675745ce736a5c4f9ffac Author: Henry Fredrick Schreiner <[email protected]> Date: Sun Aug 20 12:49:57 2017 -0400 Using setup function commit 8c718359c780c248c697b9354cda83b691203bb7 Author: Henry Fredrick Schreiner <[email protected]> Date: Sun Aug 20 10:03:54 2017 -0400 Removing globaliostream, combined from others commit 55e372f74ba73fce2c763c112d70036a8ea3ec4b Author: Henry Fredrick Schreiner <[email protected]> Date: Sat Aug 19 20:32:17 2017 -0400 Move to context manager method commit 7738d2fc7c47b1bd652f03a3c3246672a049934b Author: Henry Fredrick Schreiner <[email protected]> Date: Sat Aug 19 17:15:26 2017 -0400 Addressing first four comments
Squashed commit of the following: commit a310fa30069de7de0b92019e7736939d52fb725e Author: Henry Fredrick Schreiner <[email protected]> Date: Mon Aug 21 14:50:49 2017 -0700 Dropping a class and simpler code, from @dean0x7d commit 2af3855adb781716ed22db1aa07e09226aff3d1f Author: Henry Fredrick Schreiner <[email protected]> Date: Mon Aug 21 14:47:26 2017 -0700 Add license to header
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
Also added `module_local` to `add_ostream_redirect`.
There was also a missing header in |
The Looks good, thanks! |
Merged! Thanks, @henryiii!
It was added very recently, so I also didn't remember to add it initially, but it looks like a good fit for |
Great, thanks for the help polishing! Looking forward quite a few of the features in 2.2! |
This is the
std::cout
redirection discussed in #1005. The new docs:Capturing standard output from ostream
Often, a library will use the streams
std::cout
andstd::cerr
toprint, but this does not play well with Python's standard
sys.stdout
and
sys.stderr
redirection. Replacing a library's printing withpy::print
may not be feasible. This can be fixed using a guardaround the library function that redirects output to the corresponding
python streams:
This method respects flushes on the output streams, and will flush if
needed when the scoped guard is destroyed. This allows the output to be
redirected in real time, such as to a Jupyter notebook. The two
arguments, the C++ stream and the Python output, are optional, and
default to standard output if not given. An extra type,
py::scoped_estream_redirect
, is identical execpt for defaulting to theerror stream; this can be useful with
py::call_guard
, which allowsmultiple items, but uses the default constuctor:
The redirection can also be done in python with the addition of a
context manager, using the
py::add_ostream_redirect()
function:The name in Python defaults to
ostream_redirect
if no name is passed.This creates the following context manager in Python:
The added context manager defaults to redirecting both streams, though
you can use the keyword arguments to disable one of the streams if
needed.
Closes #1005.