如何在C++应用中嵌入Python3.8+并正确使用虚拟环境?
问题背景
此前在C++应用中通过Py_SetPythonHome(venv_path)配合Py_Initialize()嵌入Python2.7虚拟环境,迁移至Python3后使用3.8引入的隔离式初始化配置API时,直接设置config.home为虚拟环境路径,出现了无法找到基础Python库的初始化错误(init_fs_encoding: failed to get the Python codec of the filesystem encoding)。
虚拟环境通过以下命令创建:
py -3.10 -m venv C:\path\to\venv
原错误代码:
PyConfig config; PyConfig_InitIsolatedConfig(&config); auto venv_path = L"C:\\path\\to\\venv"; PyConfig_SetString(&config, &config.home, venv_path); status = Py_InitializeFromConfig(&config); PyConfig_Clear(&config); if (PyStatus_Exception(status)) { std::cout << "status.func: " << (status.func ? status.func : "N/A") << '\n'; std::cout << "status.err_msg: " << (status.err_msg ? status.err_msg : "N/A") << '\n'; }
错误原因:直接设置config.home会让Python将虚拟环境目录视为完整的基础安装目录,忽略了虚拟环境根目录下的pyvenv.cfg文件,无法获取其中记录的真实基础Python安装路径,导致编码初始化时找不到必要的库文件。
解决方案
方案一:指定虚拟环境的Python可执行文件路径(推荐)
让Python初始化API自动读取pyvenv.cfg并处理路径配置,无需手动解析配置文件:
PyConfig config; PyConfig_InitIsolatedConfig(&config); // 设置program_name为虚拟环境中的python.exe路径 const wchar_t* venv_python_path = L"C:\\path\\to\\venv\\Scripts\\python.exe"; PyConfig_SetString(&config, &config.program_name, venv_python_path); // API会自动解析pyvenv.cfg,设置正确的prefix、base_prefix等路径参数 PyStatus status = Py_InitializeFromConfig(&config); PyConfig_Clear(&config); if (PyStatus_Exception(status)) { std::cout << "status.func: " << (status.func ? status.func : "N/A") << '\n'; std::cout << "status.err_msg: " << (status.err_msg ? status.err_msg : "N/A") << '\n'; }
原理:当program_name指向虚拟环境的Python可执行文件时,初始化逻辑会自动定位到同级目录的pyvenv.cfg,读取其中的home字段设置base_prefix和base_exec_prefix,同时将虚拟环境路径设为prefix和exec_prefix,完全符合虚拟环境的路径规则。
方案二:手动读取pyvenv.cfg配置基础路径
如果无法依赖虚拟环境中的python.exe文件,可以手动读取pyvenv.cfg中的基础Python路径:
#include <fstream> #include <string> #include <iostream> // 从pyvenv.cfg中提取基础Python安装路径 std::wstring get_base_python_path(const std::wstring& venv_path) { std::wstring cfg_file_path = venv_path + L"\\pyvenv.cfg"; std::wifstream cfg_file(cfg_file_path); if (!cfg_file.is_open()) { std::cerr << "Failed to open pyvenv.cfg" << std::endl; return L""; } std::wstring line; while (std::getline(cfg_file, line)) { // 匹配pyvenv.cfg中的home字段 if (line.substr(0, 5) == L"home=") { return line.substr(5); } } return L""; } int main() { PyConfig config; PyConfig_InitIsolatedConfig(&config); const wchar_t* venv_path = L"C:\\path\\to\\venv"; std::wstring base_python_path = get_base_python_path(venv_path); if (!base_python_path.empty()) { // 设置虚拟环境作为当前prefix和exec_prefix PyConfig_SetString(&config, &config.prefix, venv_path); PyConfig_SetString(&config, &config.exec_prefix, venv_path); // 设置基础Python安装路径作为base_prefix和base_exec_prefix PyConfig_SetString(&config, &config.base_prefix, base_python_path.c_str()); PyConfig_SetString(&config, &config.base_exec_prefix, base_python_path.c_str()); } PyStatus status = Py_InitializeFromConfig(&config); PyConfig_Clear(&config); if (PyStatus_Exception(status)) { std::cout << "status.func: " << (status.func ? status.func : "N/A") << '\n'; std::cout << "status.err_msg: " << (status.err_msg ? status.err_msg : "N/A") << '\n'; } return 0; }
原理:pyvenv.cfg中记录了创建虚拟环境时的基础Python安装路径(home=字段),手动读取该值并分别设置虚拟环境路径和基础Python路径,让Python正确区分虚拟环境依赖和基础库依赖。
内容的提问来源于stack exchange,提问作者Martin.

