Search code examples
pythonc++pybind11

pybind11 Redirect python sys.stdout to c++ from print()


I have a c++ program with a pybind11 embedded python interpreter, executing the following python file, it prints directly to std::cout

# test.py
print("text")

The c++ program executing the file:

#include <pybind11/embed.h>

namespace py = pybind11;

int main() {
    py::scoped_interpreter guard{};
    py::eval_file("test.py");
}

Other solutions I found required modifying the python file - how can I redirect python sys.stdout to c++ as a std::string without modifying the python code using only the print() statement?


Solution

  • This github issue describes a way: https://github.com/pybind/pybind11/issues/1622

    Copying the code from that issue verbatim. The following bit was recommended to make it work:

    #include <pybind11/pybind11.h>
    namespace py = pybind11;
    

    From the issue:

    class PyStdErrOutStreamRedirect {
        py::object _stdout;
        py::object _stderr;
        py::object _stdout_buffer;
        py::object _stderr_buffer;
    public:
        PyStdErrOutStreamRedirect() {
            auto sysm = py::module::import("sys");
            _stdout = sysm.attr("stdout");
            _stderr = sysm.attr("stderr");
            auto stringio = py::module::import("io").attr("StringIO");
            _stdout_buffer = stringio();  // Other filelike object can be used here as well, such as objects created by pybind11
            _stderr_buffer = stringio();
            sysm.attr("stdout") = _stdout_buffer;
            sysm.attr("stderr") = _stderr_buffer;
        }
        std::string stdoutString() {
            _stdout_buffer.attr("seek")(0);
            return py::str(_stdout_buffer.attr("read")());
        }
        std::string stderrString() {
            _stderr_buffer.attr("seek")(0);
            return py::str(_stderr_buffer.attr("read")());
        }
        ~PyStdErrOutStreamRedirect() {
            auto sysm = py::module::import("sys");
            sysm.attr("stdout") = _stdout;
            sysm.attr("stderr") = _stderr;
        }
    };
    

    Usage:

    {
        PyStdErrOutStreamRedirect pyOutputRedirect{};
        py::print("hello world");
        // Other noisy python functions can be put here
        assert(pyOutputRedirect.stdoutString() == "hello world\n")
    }
    // sys.stdout is back to its original state