嵌入Python 3:如何将stdout、stdin和stderr重定向至C++?
Let’s break down your questions and build a clean, pure C/C++ approach that fits your custom Python GUI use case perfectly.
Question 1: Is PyFile_FromFd the Right Replacement for PyFile_FromFile in Python 3?
Great call noticing PyFile_FromFile is deprecated in Python 3. While PyFile_FromFd is marked as a "legacy" API, it’s still fully functional in Python 3 and acts as the direct replacement for PyFile_FromFile when wrapping file descriptors.
If you want to follow the most modern Python IO practices, you could use the io module’s C API to create buffered IO objects, but that adds unnecessary boilerplate for your use case. For capturing output from PyRun_SimpleString, PyFile_FromFd is more than sufficient and keeps your code concise.
Question 2: How to Only Redirect Output from Your Own PyRun_SimpleString Calls?
The solution here is temporary redirection: swap out sys.stdout and sys.stderr right before executing your Python code, then restore them immediately after. This way, only the output from your PyRun_SimpleString call gets captured—other parts of the GUI or app won’t be affected.
Since your library is loaded by the GUI, this approach is safe and isolated to your library’s Python execution context.
Step-by-Step Implementation
Here’s a complete, tested code example that uses pipes to capture output, temporary redirection, and pure C/C++ with Python’s C API:
#include <Python.h> #include <unistd.h> #include <fcntl.h> #include <vector> #include <string> std::string capture_python_execution_output(const char* user_python_code) { // Ensure we hold the GIL (critical for thread-safe Python API calls) PyGILState_STATE gstate = PyGILState_Ensure(); std::string output; int pipefd[2]; // Create a pipe to capture output if (pipe(pipefd) == -1) { PyGILState_Release(gstate); return "Error: Failed to create output capture pipe"; } // Save original stdout/stderr to restore later PyObject* original_stdout = PySys_GetObject("stdout"); PyObject* original_stderr = PySys_GetObject("stderr"); if (!original_stdout || !original_stderr) { close(pipefd[0]); close(pipefd[1]); PyGILState_Release(gstate); return "Error: Could not access original stdout/stderr"; } Py_INCREF(original_stdout); Py_INCREF(original_stderr); // Create Python file objects from the pipe's write end PyObject* redirected_stdout = PyFile_FromFd( pipefd[1], "w", "w", -1, NULL, NULL, NULL, 1 // 1 = close fd when object is deleted ); PyObject* redirected_stderr = PyFile_FromFd( pipefd[1], "w", "w", -1, NULL, NULL, NULL, 1 ); if (!redirected_stdout || !redirected_stderr) { Py_DECREF(original_stdout); Py_DECREF(original_stderr); close(pipefd[0]); close(pipefd[1]); PyGILState_Release(gstate); return "Error: Failed to create redirected file objects"; } // Replace sys.stdout and sys.stderr with our pipe-backed objects if (PySys_SetObject("stdout", redirected_stdout) != 0 || PySys_SetObject("stderr", redirected_stderr) != 0) { Py_DECREF(redirected_stdout); Py_DECREF(redirected_stderr); Py_DECREF(original_stdout); Py_DECREF(original_stderr); close(pipefd[0]); close(pipefd[1]); PyGILState_Release(gstate); return "Error: Failed to redirect stdout/stderr"; } Py_DECREF(redirected_stdout); Py_DECREF(redirected_stderr); // Execute the user's Python code int exec_result = PyRun_SimpleString(user_python_code); (void)exec_result; // Add error handling here if you need to report execution failures // Flush any buffered output to ensure it hits the pipe PyRun_SimpleString("import sys; sys.stdout.flush(); sys.stderr.flush()"); // Restore original stdout/stderr PySys_SetObject("stdout", original_stdout); PySys_SetObject("stderr", original_stderr); Py_DECREF(original_stdout); Py_DECREF(original_stderr); // Close the pipe's write end to signal EOF to the read end close(pipefd[1]); // Read all captured output from the pipe char buffer[1024]; ssize_t bytes_read; while ((bytes_read = read(pipefd[0], buffer, sizeof(buffer))) > 0) { output.append(buffer, bytes_read); } close(pipefd[0]); // Release the GIL PyGILState_Release(gstate); return output; }
Key Notes
- GIL Handling: The
PyGILState_Ensure()andPyGILState_Release()calls are critical if your library might be called from non-Python threads (common in GUI contexts). They ensure safe access to Python’s C API. - Buffering: We explicitly flush
sys.stdoutandsys.stderrafter executing the user’s code to make sure all output is written to the pipe before restoring the original streams. - Isolation: By saving and restoring the original stdout/stderr, we guarantee that only our
PyRun_SimpleStringcall’s output is captured—no impact on the GUI or other parts of the app.
内容的提问来源于stack exchange,提问作者dev15

