pybind11小技巧
pybind11小技巧
Section titled “pybind11小技巧”pybind11是一个简化C++和Python相互调用的一个库,相比Boost.Python更现代化,更易用。
这里,记录了一些在使用中的技巧。
在嵌入Python解释器时,如何自动设置PYTHONHOME和PYTHONPATH。
Section titled “在嵌入Python解释器时,如何自动设置PYTHONHOME和PYTHONPATH。”这个问题在Windows上会出现,在Mac和Linux上貌似都没有出现。现象是类似这样的代码:
#include <pybind11/embed.h>void main() { pybind11::scoped_interpreter guard{}; pybind11::exec("print('hello world')");}然后在Windows上运行时报错了, 错误信息类似:
Python path configuration:PYTHONHOME = (not set)PYTHONPATH = (not set)program name = 'python'isolated = 0environment = 1user site = 1import site = 1sys._base_executable = 'C:\\Users\\ben.wolfley\\Desktop\\Test3\\vsstudio\\Debug\\pybind11app.exe'sys.base_prefix = 'C:\\Users\\ben.wolfley\\Anaconda3'sys.base_exec_prefix = 'C:\\Users\\ben.wolfley\\Anaconda3'sys.executable = 'C:\\Users\\ben.wolfley\\Desktop\\Test3\\vsstudio\\Debug\\pybind11app.exe'sys.prefix = 'C:\\Users\\ben.wolfley\\Anaconda3'sys.exec_prefix = 'C:\\Users\\ben.wolfley\\Anaconda3'sys.path = ['C:\\Users\\ben.wolfley\\Anaconda3\\python38.zip','.\\DLLs','.\\lib','C:\\Users\\ben.wolfley\\Desktop\\Test3\\vsstudio\\Debug',]Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encodingPython runtime state: core initializedModuleNotFoundError: No module named 'encodings'`问题的原因在于需要设置PYTHONHOME,PYTHONHOME可以从通过环境变量来设置,也可以通过调用函数:Py_SetPythonHome来设置。
当然,更方便的方法可以通过如下代码来自动设置:
#include <boost/dll.hpp>
#ifndef _WIN32static auto pythonHome = boost::dll::symbol_location(Py_Initialize).parent_path().parent_path().string();#elsestatic auto pythonHome = boost::dll::symbol_location(Py_Initialize).parent_path().string();#endifPy_SetPythonHome(pythonHome.data());参考资料:
import C扩展时报错
Section titled “import C扩展时报错”类似这种错误:
ImportError: /root/.cache/pycchain/python38/lib/python3.8/lib-dynload/math.cpython-38-x86_64-linux-gnu.so: undefined symbol: PyFloat_Type
At: /root/.cache/pycchain/python38/lib/python3.8/datetime.py(8): <module> <frozen importlib._bootstrap>(219): _call_with_frames_removed <frozen importlib._bootstrap_external>(843): exec_module <frozen importlib._bootstrap>(686): _load_unlocked <frozen importlib._bootstrap>(975): _find_and_load_unlocked <frozen importlib._bootstrap>(991): _find_and_load /root/.cache/pycchain/python38/lib/python3.8/site-packages/dateutil/parser/_parser.py(33): <module> <frozen importlib._bootstrap>(219): _call_with_frames_removed <frozen importlib._bootstrap_external>(843): exec_module <frozen importlib._bootstrap>(686): _load_unlocked <frozen importlib._bootstrap>(975): _find_and_load_unlocked <frozen importlib._bootstrap>(991): _find_and_load /root/.cache/pycchain/python38/lib/python3.8/site-packages/dateutil/parser/__init__.py(2): <module> <frozen importlib._bootstrap>(219): _call_with_frames_removed <frozen importlib._bootstrap_external>(843): exec_module <frozen importlib._bootstrap>(686): _load_unlocked <frozen importlib._bootstrap>(975): _find_and_load_unlocked <frozen importlib._bootstrap>(991): _find_and_load <string>(2): <module>当我在C++内嵌Python解释器时,其实倒没有遇到这个问题,但我通过JNI实现Java内嵌Python解释器时,就遇到这个问题了。并且这个问题只出现在Linux上。
解决办法:
// 在初始化阶段#ifdef __linux__ auto pythonLibrary = boost::dll::symbol_location(Py_Initialize).string(); python = dlopen(pythonLibrary.c_str(), RTLD_NOW | RTLD_GLOBAL);#endif
// 在结束阶段#ifdef __linux__ if (python != nullptr) { dlclose(python); python = nullptr; }#endif参考资料:
如何捕获stdout和stderr
Section titled “如何捕获stdout和stderr”实现方法倒是很简单,也就是把Python的stdout和stderr分别指向一个类的属性中:
#include <pybind11/iostream.h>
/** * @class PyStdErrOutStreamRedirect * @brief 用于重定向Python的标准输出和错误输出流的类 */class PyStdErrOutStreamRedirect { pybind11::object _stdout; ///< 原始的标准输出流 pybind11::object _stderr; ///< 原始的错误输出流 pybind11::object _stdout_buffer; ///< 重定向后的标准输出流 pybind11::object _stderr_buffer; ///< 重定向后的错误输出流public: /** * @brief 构造函数,进行流的重定向 */ PyStdErrOutStreamRedirect() { auto sysm = pybind11::module::import("sys"); _stdout = sysm.attr("stdout"); _stderr = sysm.attr("stderr"); auto stringio = pybind11::module::import("io").attr("StringIO"); _stdout_buffer = stringio(); _stderr_buffer = stringio(); sysm.attr("stdout") = _stdout_buffer; sysm.attr("stderr") = _stderr_buffer; }
/** * @brief 获取重定向后的标准输出流的内容 * @return 标准输出流的内容 */ std::string stdoutString() { _stdout_buffer.attr("seek")(0); return pybind11::str(_stdout_buffer.attr("read")()); }
/** * @brief 获取重定向后的错误输出流的内容 * @return 错误输出流的内容 */ std::string stderrString() { _stderr_buffer.attr("seek")(0); return pybind11::str(_stderr_buffer.attr("read")()); }
/** * @brief 析构函数,恢复原始的流 */ virtual ~PyStdErrOutStreamRedirect() { auto sysm = pybind11::module::import("sys"); sysm.attr("stdout") = _stdout; sysm.attr("stderr") = _stderr; }};
// 然后使用时,只需要即可:PyStdErrOutStreamRedirect pyOutputRedirect;