如何通过C API在嵌入式Python中结合virtualenv调用Py_Initialize?
问题描述
我在C++应用中使用稳定版C API调用嵌入式Python解释器执行Python脚本,目前运行正常,但希望扩展功能,配置virtualenv路径以替代系统Python包。
我了解到需要在调用Py_Initialize前设置相关变量来启用virtualenv,尝试了Py_SetPythonHome、Py_SetPath、Py_SetProgramName与PATH、VIRTUAL_ENV、PYTHONPATH环境变量的多种组合,但均未生效。
我的最后一次尝试代码如下:
std::string old_path = utils::Environment::getEnvironmentVariable("PATH").value(); utils::Environment::setEnvironmentVariable("PATH", ((virtualenv_path_ / "bin").string() + ":" + old_path).c_str()); utils::Environment::setEnvironmentVariable("VIRTUAL_ENV", virtualenv_path_.string().c_str()); auto lib_path = virtualenv_path_ / "lib" ; std::string python_dir_name; for (auto const& dir_entry : std::filesystem::directory_iterator{lib_path}) { if (minifi::utils::string::startsWith(dir_entry.path().filename().string(), "python")) { python_dir_name = dir_entry.path().filename().string(); break; } } static const auto package_path = lib_path / python_dir_name / "site-packages"; static const auto python_dir_path = lib_path / python_dir_name; static size_t virtualenv_path_size = virtualenv_path_.string().size(); Py_SetPythonHome(Py_DecodeLocale(virtualenv_path_.c_str(), &virtualenv_path_size)); utils::Environment::setEnvironmentVariable("PYTHONPATH", (package_path.string() + "/:" + lib_path.string() + "/").c_str());
启动应用后目录设置看似正确,但仍报错(virtualenv路径为/home/user/pythonrunnerapp/pyenv):
Python path configuration: PYTHONHOME = '/home/user/pythonrunnerapp/pyenv' PYTHONPATH = '/home/user/pythonrunnerapp/pyenv/lib/python3.10/site-packages/:/home/user/pythonrunnerapp/pyenv/lib/' program name = 'python3' isolated = 0 environment = 1 user site = 1 import site = 1 sys._base_executable = '/home/user/pythonrunnerapp/pyenv/bin/python3' sys.base_prefix = '/home/user/pythonrunnerapp/pyenv' sys.base_exec_prefix = '/home/user/pythonrunnerapp/pyenv' sys.platlibdir = 'lib' sys.executable = '/home/user/pythonrunnerapp/pyenv/bin/python3' sys.prefix = '/home/user/pythonrunnerapp/pyenv' sys.exec_prefix = '/home/user/pythonrunnerapp/pyenv' sys.path = [ '/home/user/pythonrunnerapp/pyenv/lib/python3.10/site-packages/', '/home/user/pythonrunnerapp/pyenv/lib/', '/home/user/pythonrunnerapp/pyenv/lib/python310.zip', '/home/user/pythonrunnerapp/pyenv/lib/python3.10', '/home/user/pythonrunnerapp/pyenv/lib/python3.10/lib-dynload', ] Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding Python runtime state: core initialized ModuleNotFoundError: No module named 'encodings' Current thread 0x00007f33a4b50780 (most recent call first): <no Python frame>
若提前在shell中激活环境再运行应用,virtualenv可正常工作,但我希望能在应用内按需激活。请问无需预激活时,如何在virtualenv中正确使用嵌入式Python?
解决方案
出现ModuleNotFoundError: No module named 'encodings'的核心原因是Python初始化时无法找到核心内置模块的正确路径——虽然sys.path里看似包含了必要目录,但手动设置PYTHONPATH会干扰Python自动推导核心模块路径的逻辑,且缺少Py_SetProgramName的正确配置。以下是修正后的实现步骤:
关键要点
- 必须设置
Py_SetProgramName:指向virtualenv中的Python可执行文件,这是Python推导核心模块路径的关键依据。 - 避免手动覆盖
PYTHONPATH:改用Py_SetPath构建包含virtualenv站点包的路径,同时保留Python核心模块的默认路径。 - 确保
Py_SetPythonHome指向virtualenv根目录:让Python明确知道虚拟环境的位置。 - 设置必要环境变量:
VIRTUAL_ENV和PATH确保工具链和环境识别正确。
修正后的代码
// 虚拟环境根路径 std::filesystem::path virtualenv_path_ = "/home/user/pythonrunnerapp/pyenv"; // 1. 设置PATH,优先使用virtualenv的bin目录 std::string old_path = utils::Environment::getEnvironmentVariable("PATH").value_or(""); std::string new_path = (virtualenv_path_ / "bin").string() + ":" + old_path; utils::Environment::setEnvironmentVariable("PATH", new_path.c_str()); // 2. 设置VIRTUAL_ENV环境变量 utils::Environment::setEnvironmentVariable("VIRTUAL_ENV", virtualenv_path_.string().c_str()); // 3. 找到Python版本对应的lib子目录(比如python3.10) auto lib_path = virtualenv_path_ / "lib"; std::string python_dir_name; for (const auto& dir_entry : std::filesystem::directory_iterator{lib_path}) { if (dir_entry.is_directory() && minifi::utils::string::startsWith(dir_entry.path().filename().string(), "python")) { python_dir_name = dir_entry.path().filename().string(); break; } } if (python_dir_name.empty()) { // 处理找不到Python目录的错误 throw std::runtime_error("Failed to find Python directory in virtualenv"); } // 4. 配置Python核心路径和站点包路径 auto python_lib_path = lib_path / python_dir_name; auto site_packages_path = python_lib_path / "site-packages"; // 构建完整的Python路径列表:站点包 + 核心模块路径 std::vector<std::string> path_components = { site_packages_path.string(), python_lib_path.string(), (python_lib_path / "lib-dynload").string(), (python_lib_path / "python310.zip").string() // 根据实际Python版本调整 }; // 用分号(Windows)或冒号(Unix)分隔路径 std::string py_path; #ifdef _WIN32 const char path_sep = ';'; #else const char path_sep = ':'; #endif for (size_t i = 0; i < path_components.size(); ++i) { if (i > 0) py_path += path_sep; py_path += path_components[i]; } // 5. 设置Python程序名(必须指向virtualenv内的python可执行文件) auto python_executable = virtualenv_path_ / "bin" / "python3"; size_t prog_name_size; wchar_t* prog_name = Py_DecodeLocale(python_executable.string().c_str(), &prog_name_size); Py_SetProgramName(prog_name); // 6. 设置PythonHome size_t home_size; wchar_t* python_home = Py_DecodeLocale(virtualenv_path_.string().c_str(), &home_size); Py_SetPythonHome(python_home); // 7. 设置Python路径 wchar_t* py_path_wide = Py_DecodeLocale(py_path.c_str(), nullptr); Py_SetPath(py_path_wide); // 8. 初始化Python解释器 Py_Initialize(); // 注意:初始化完成后,释放解码的字符串避免内存泄漏 PyMem_RawFree(prog_name); PyMem_RawFree(python_home); PyMem_RawFree(py_path_wide);
额外说明
- 不要手动设置
PYTHONPATH环境变量:它会覆盖Python自动生成的核心模块路径,导致encodings这类内置模块找不到。 - 路径顺序很重要:把
site-packages放在前面,确保优先加载虚拟环境中的包,而不是系统包。 - 版本适配:代码中的
python310.zip需要根据虚拟环境中的Python版本调整(比如Python3.9对应python39.zip)。 - 内存管理:
Py_DecodeLocale分配的内存需要在Python初始化完成后手动释放,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Lord Gamez
相关产品推荐
相关产品推荐

