将python sys.stdout重定向到c++,用于print()函数。

5
我有一个C++程序,其中嵌入了pybind11的Python解释器。执行以下Python文件时,它会直接输出到std::cout
# test.py
print("text")
#include <pybind11/embed.h>

namespace py = pybind11;

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

我该如何在不修改 Python 代码的情况下,仅使用 print() 语句将 Python sys.stdout 重定向到 C++ 中作为 std::string?我发现的其他解决方案需要修改 Python 文件。
3个回答

3
这个Github问题描述了一种方法:https://github.com/pybind/pybind11/issues/1622 复制该问题中的代码。以下部分被推荐使用以使其正常工作:
#include <pybind11/pybind11.h>
namespace py = pybind11;

来自问题:

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;
    }
};

使用方法:

{
    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

1
在这个上下文中,“capture”是什么意思?无论是Python还是C++,编写都要通过操作系统。如果意图只是为了消除输出,将其写入文件、发送到另一个进程等等,可以在该级别拦截所有输出。
以下是一个示例,它在Python脚本的持续时间内压缩所有标准输出,然后在此之后恢复,此时标准输出的行为与以前相同(对于Python和其他语言):
#include <pybind11/embed.h>
#include <unistd.h>

namespace py = pybind11;

int main() {

    auto fdo = fileno(stdout);
    auto savefd = dup(fdo);
    auto f = fopen("/dev/null", "w");
    dup2(fileno(f), fdo);

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

    fflush(stdout);
    dup2(savefd, fdo);
}

0

我的实现如下:

原则是实现一个模块来替换sys.stdout

#include <iostream>
#include <pybind11/embed.h>

PYBIND11_EMBEDDED_MODULE(my_sys, m) {
    struct my_stdout {
        my_stdout() = default;
        my_stdout(const my_stdout &) = default;
        my_stdout(my_stdout &&) = default;
    };

    py::class_<my_stdout> my_stdout(m, "my_stdout");
    my_stdout.def_static("write", [](py::object buffer) {
        std::cout << buffer.cast<std::string>();
    });
    my_stdout.def_static("flush", []() {
        std::cout << std::flush;
    });

    m.def("hook_stdout", []() {
        auto py_sys = py::module::import("sys");
        auto my_sys = py::module::import("my_sys");
        py_sys.attr("stdout") = my_sys.attr("my_stdout");
    });
}

auto main(int argc, char **argv) -> int {
    py::scoped_interpreter python;

#if 0
    py_stdout.attr("write") = py::cpp_function([](py::object info) {
        std::cout << "info" << std::endl;
    });
#else
    py::module::import("my_sys").attr("hook_stdout")();
#endif

    py::print("Hello, World!\n")

    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接