You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 06:50:55